ai-engineering-init 1.3.4 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/skill-forced-eval.js +2 -0
- package/.claude/settings.json +3 -3
- package/.claude/skills/add-skill/SKILL.md +79 -32
- package/.claude/skills/api-development/SKILL.md +83 -377
- package/.claude/skills/architecture-design/SKILL.md +138 -632
- package/.claude/skills/backend-annotations/SKILL.md +134 -506
- package/.claude/skills/banana-image/SKILL.md +10 -3
- package/.claude/skills/brainstorm/SKILL.md +103 -535
- package/.claude/skills/bug-detective/SKILL.md +147 -1097
- package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
- package/.claude/skills/code-patterns/SKILL.md +116 -426
- package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.claude/skills/crud-development/SKILL.md +64 -304
- package/.claude/skills/data-permission/SKILL.md +105 -412
- package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.claude/skills/file-oss-management/SKILL.md +106 -714
- package/.claude/skills/file-oss-management/references/entities.md +105 -0
- package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
- package/.claude/skills/leniu-api-development/SKILL.md +142 -626
- package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
- package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
- package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
- package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
- package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
- package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
- package/.claude/skills/leniu-java-export/SKILL.md +94 -379
- package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
- package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +111 -365
- package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
- package/.claude/skills/mysql-debug/SKILL.md +364 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
- package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
- package/.claude/skills/openspec-explore/SKILL.md +10 -1
- package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
- package/.claude/skills/openspec-new-change/SKILL.md +9 -1
- package/.claude/skills/openspec-onboard/SKILL.md +15 -130
- package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
- package/.claude/skills/performance-doctor/SKILL.md +110 -434
- package/.claude/skills/redis-cache/SKILL.md +89 -595
- package/.claude/skills/redis-cache/references/listeners.md +23 -0
- package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
- package/.claude/skills/security-guard/SKILL.md +137 -532
- package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
- package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.claude/skills/sms-mail/SKILL.md +116 -574
- package/.claude/skills/sms-mail/references/mail-config.md +88 -0
- package/.claude/skills/sms-mail/references/sms-config.md +74 -0
- package/.claude/skills/social-login/SKILL.md +112 -514
- package/.claude/skills/social-login/references/provider-configs.md +118 -0
- package/.claude/skills/tenant-management/SKILL.md +129 -444
- package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.claude/skills/test-development/SKILL.md +86 -540
- package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
- package/.claude/skills/utils-toolkit/SKILL.md +52 -305
- package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.claude/skills/websocket-sse/SKILL.md +105 -550
- package/.claude/skills/workflow-engine/SKILL.md +147 -502
- package/.codex/skills/add-skill/SKILL.md +79 -32
- package/.codex/skills/api-development/SKILL.md +172 -599
- package/.codex/skills/architecture-design/SKILL.md +138 -504
- package/.codex/skills/backend-annotations/SKILL.md +134 -496
- package/.codex/skills/banana-image/SKILL.md +10 -3
- package/.codex/skills/brainstorm/SKILL.md +103 -535
- package/.codex/skills/bug-detective/SKILL.md +147 -1097
- package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
- package/.codex/skills/code-patterns/SKILL.md +120 -282
- package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.codex/skills/crud-development/SKILL.md +64 -292
- package/.codex/skills/data-permission/SKILL.md +108 -407
- package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.codex/skills/database-ops/SKILL.md +8 -154
- package/.codex/skills/error-handler/SKILL.md +10 -0
- package/.codex/skills/file-oss-management/SKILL.md +106 -714
- package/.codex/skills/file-oss-management/references/entities.md +105 -0
- package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
- package/.codex/skills/git-workflow/SKILL.md +27 -5
- package/.codex/skills/leniu-api-development/SKILL.md +142 -626
- package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
- package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
- package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
- package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
- package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
- package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
- package/.codex/skills/leniu-java-export/SKILL.md +94 -379
- package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
- package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +111 -365
- package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
- package/.codex/skills/mysql-debug/SKILL.md +364 -0
- package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
- package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
- package/.codex/skills/openspec-explore/SKILL.md +10 -1
- package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
- package/.codex/skills/openspec-new-change/SKILL.md +9 -1
- package/.codex/skills/openspec-onboard/SKILL.md +15 -130
- package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
- package/.codex/skills/performance-doctor/SKILL.md +110 -434
- package/.codex/skills/project-navigator/SKILL.md +20 -1
- package/.codex/skills/redis-cache/SKILL.md +93 -589
- package/.codex/skills/redis-cache/references/listeners.md +23 -0
- package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
- package/.codex/skills/security-guard/SKILL.md +141 -527
- package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
- package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.codex/skills/sms-mail/SKILL.md +116 -574
- package/.codex/skills/sms-mail/references/mail-config.md +88 -0
- package/.codex/skills/sms-mail/references/sms-config.md +74 -0
- package/.codex/skills/social-login/SKILL.md +112 -514
- package/.codex/skills/social-login/references/provider-configs.md +118 -0
- package/.codex/skills/store-pc/SKILL.md +258 -383
- package/.codex/skills/tenant-management/SKILL.md +129 -444
- package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.codex/skills/test-development/SKILL.md +86 -540
- package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
- package/.codex/skills/ui-pc/SKILL.md +350 -387
- package/.codex/skills/utils-toolkit/SKILL.md +52 -283
- package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.codex/skills/websocket-sse/SKILL.md +105 -550
- package/.codex/skills/workflow-engine/SKILL.md +147 -502
- package/.cursor/hooks/cursor-skill-eval.js +53 -1
- package/.cursor/hooks.json +3 -3
- package/.cursor/skills/add-skill/SKILL.md +79 -32
- package/.cursor/skills/api-development/SKILL.md +83 -377
- package/.cursor/skills/architecture-design/SKILL.md +138 -632
- package/.cursor/skills/backend-annotations/SKILL.md +134 -506
- package/.cursor/skills/banana-image/SKILL.md +10 -3
- package/.cursor/skills/brainstorm/SKILL.md +103 -535
- package/.cursor/skills/bug-detective/SKILL.md +147 -1097
- package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
- package/.cursor/skills/code-patterns/SKILL.md +116 -426
- package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.cursor/skills/crud-development/SKILL.md +64 -304
- package/.cursor/skills/data-permission/SKILL.md +105 -412
- package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.cursor/skills/file-oss-management/SKILL.md +106 -714
- package/.cursor/skills/file-oss-management/references/entities.md +105 -0
- package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
- package/.cursor/skills/git-workflow/SKILL.md +27 -5
- package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
- package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
- package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
- package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
- package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
- package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
- package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
- package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
- package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
- package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.cursor/skills/leniu-report-customization/SKILL.md +111 -365
- package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
- package/.cursor/skills/mysql-debug/SKILL.md +364 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
- package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-explore/SKILL.md +10 -1
- package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
- package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
- package/.cursor/skills/performance-doctor/SKILL.md +110 -434
- package/.cursor/skills/redis-cache/SKILL.md +89 -595
- package/.cursor/skills/redis-cache/references/listeners.md +23 -0
- package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
- package/.cursor/skills/security-guard/SKILL.md +137 -532
- package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
- package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.cursor/skills/sms-mail/SKILL.md +116 -574
- package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
- package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
- package/.cursor/skills/social-login/SKILL.md +112 -514
- package/.cursor/skills/social-login/references/provider-configs.md +118 -0
- package/.cursor/skills/tenant-management/SKILL.md +129 -444
- package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.cursor/skills/test-development/SKILL.md +86 -540
- package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
- package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
- package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.cursor/skills/websocket-sse/SKILL.md +105 -550
- package/.cursor/skills/workflow-engine/SKILL.md +147 -502
- package/package.json +1 -1
|
@@ -19,331 +19,158 @@ description: |
|
|
|
19
19
|
|
|
20
20
|
# 后端性能优化指南
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## 目录
|
|
25
|
-
|
|
26
|
-
- [性能问题诊断流程](#性能问题诊断流程)
|
|
27
|
-
- [MyBatis-Plus 查询优化](#1-mybatis-plus-查询优化)
|
|
28
|
-
- [SQL 优化](#2-sql-优化)
|
|
29
|
-
- [缓存优化](#3-缓存优化)
|
|
30
|
-
- [多租户优化](#4-多租户优化)
|
|
31
|
-
- [批量操作优化](#5-批量操作优化)
|
|
32
|
-
- [接口优化](#6-接口优化)
|
|
33
|
-
- [性能日志分析](#7-性能日志分析)
|
|
34
|
-
- [性能指标与监控工具](#8-性能指标与监控工具)
|
|
35
|
-
- [常见性能问题速查](#常见性能问题速查)
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## 性能问题诊断流程
|
|
22
|
+
## 诊断流程
|
|
40
23
|
|
|
41
24
|
```
|
|
42
|
-
1.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
2. 分析原因
|
|
48
|
-
├─ 使用工具测量(p6spy/Arthas/日志分析)
|
|
49
|
-
└─ 找出瓶颈点
|
|
25
|
+
1. 定位 -> 接口层面(响应时间) / SQL层面(慢查询) / 缓存层面(命中率)
|
|
26
|
+
2. 分析 -> p6spy SQL 日志 / Arthas JVM 诊断 / 日志分析
|
|
27
|
+
3. 优化 -> 索引 / 缓存 / 批量处理
|
|
28
|
+
4. 验证 -> 对比优化前后指标
|
|
29
|
+
```
|
|
50
30
|
|
|
51
|
-
|
|
52
|
-
└─ 针对性优化(索引/缓存/批量处理)
|
|
31
|
+
### 性能指标
|
|
53
32
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
33
|
+
| 指标 | 良好 | 需优化 | 工具 |
|
|
34
|
+
|------|------|--------|------|
|
|
35
|
+
| 接口响应 | < 200ms | > 500ms | p6spy/SkyWalking |
|
|
36
|
+
| 数据库查询 | < 50ms | > 200ms | p6spy SQL 日志 |
|
|
37
|
+
| 内存使用 | < 70% | > 85% | Arthas/JVM 监控 |
|
|
57
38
|
|
|
58
39
|
---
|
|
59
40
|
|
|
60
41
|
## 1. MyBatis-Plus 查询优化
|
|
61
42
|
|
|
62
|
-
>
|
|
43
|
+
> 源码:`ruoyi-modules/ruoyi-demo/.../TestDemoServiceImpl.java:62-73`
|
|
63
44
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
### 🔴 查询构建规范(本项目规范)
|
|
45
|
+
### 查询构建规范
|
|
67
46
|
|
|
68
47
|
```java
|
|
69
48
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
70
49
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
71
50
|
|
|
72
|
-
// ✅ 正确:本项目标准写法
|
|
73
51
|
private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) {
|
|
74
52
|
Map<String, Object> params = bo.getParams();
|
|
75
53
|
LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery();
|
|
76
|
-
|
|
77
|
-
// 条件判断+查询条件
|
|
78
54
|
lqw.eq(bo.getDeptId() != null, TestDemo::getDeptId, bo.getDeptId());
|
|
79
|
-
lqw.eq(bo.getUserId() != null, TestDemo::getUserId, bo.getUserId());
|
|
80
55
|
lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey());
|
|
81
56
|
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
|
|
82
57
|
TestDemo::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
|
|
83
|
-
|
|
84
58
|
return lqw;
|
|
85
59
|
}
|
|
86
|
-
|
|
87
|
-
// 使用
|
|
88
|
-
@Override
|
|
89
|
-
public TableDataInfo<TestDemoVo> queryPageList(TestDemoBo bo, PageQuery pageQuery) {
|
|
90
|
-
LambdaQueryWrapper<TestDemo> lqw = buildQueryWrapper(bo);
|
|
91
|
-
Page<TestDemoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
|
92
|
-
return TableDataInfo.build(result);
|
|
93
|
-
}
|
|
94
60
|
```
|
|
95
61
|
|
|
96
|
-
###
|
|
62
|
+
### 更新构建规范
|
|
97
63
|
|
|
98
|
-
>
|
|
64
|
+
> 源码:`ruoyi-modules/ruoyi-system/.../SysUserServiceImpl.java:383-404`
|
|
99
65
|
|
|
100
66
|
```java
|
|
101
67
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
102
68
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return baseMapper.update(null,
|
|
116
|
-
new LambdaUpdateWrapper<SysUser>()
|
|
117
|
-
.set(ObjectUtil.isNotNull(user.getNickName()), SysUser::getNickName, user.getNickName())
|
|
118
|
-
.set(SysUser::getPhonenumber, user.getPhonenumber())
|
|
119
|
-
.set(SysUser::getEmail, user.getEmail())
|
|
120
|
-
.set(SysUser::getSex, user.getSex())
|
|
121
|
-
.eq(SysUser::getUserId, user.getUserId()));
|
|
122
|
-
}
|
|
69
|
+
// 精确更新
|
|
70
|
+
baseMapper.update(null,
|
|
71
|
+
new LambdaUpdateWrapper<SysUser>()
|
|
72
|
+
.set(SysUser::getStatus, status)
|
|
73
|
+
.eq(SysUser::getUserId, userId));
|
|
74
|
+
|
|
75
|
+
// 条件更新(非null才设置)
|
|
76
|
+
baseMapper.update(null,
|
|
77
|
+
new LambdaUpdateWrapper<SysUser>()
|
|
78
|
+
.set(ObjectUtil.isNotNull(user.getNickName()), SysUser::getNickName, user.getNickName())
|
|
79
|
+
.set(SysUser::getPhonenumber, user.getPhonenumber())
|
|
80
|
+
.eq(SysUser::getUserId, user.getUserId()));
|
|
123
81
|
```
|
|
124
82
|
|
|
125
|
-
###
|
|
126
|
-
|
|
127
|
-
MyBatis-Plus 的条件构造器支持条件判断,自动忽略不满足的条件:
|
|
128
|
-
|
|
129
|
-
```java
|
|
130
|
-
// ❌ 传统写法(每个条件都要判断,代码冗长)
|
|
131
|
-
LambdaQueryWrapper<Xxx> wrapper = new LambdaQueryWrapper<>();
|
|
132
|
-
if (bo.getId() != null) {
|
|
133
|
-
wrapper.eq(Xxx::getId, bo.getId());
|
|
134
|
-
}
|
|
135
|
-
if (StringUtils.isNotBlank(bo.getName())) {
|
|
136
|
-
wrapper.like(Xxx::getName, bo.getName());
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ✅ MyBatis-Plus 条件构造(自动处理 null 和空字符串)
|
|
140
|
-
LambdaQueryWrapper<Xxx> lqw = Wrappers.lambdaQuery();
|
|
141
|
-
lqw.eq(bo.getId() != null, Xxx::getId, bo.getId()); // null 自动跳过
|
|
142
|
-
lqw.like(StringUtils.isNotBlank(bo.getName()), Xxx::getName, bo.getName()); // 空字符串自动跳过
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### 批量更新优化
|
|
83
|
+
### 批量更新
|
|
146
84
|
|
|
147
85
|
```java
|
|
148
|
-
// ✅ 推荐:使用 Db 工具类批量更新
|
|
149
86
|
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
|
150
87
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
.map(id -> {
|
|
154
|
-
Xxx entity = new Xxx();
|
|
155
|
-
entity.setId(id);
|
|
156
|
-
entity.setStatus(status);
|
|
157
|
-
return entity;
|
|
158
|
-
})
|
|
159
|
-
.toList();
|
|
160
|
-
Db.updateBatchById(list);
|
|
161
|
-
}
|
|
88
|
+
// 推荐:Db 工具类批量更新
|
|
89
|
+
Db.updateBatchById(entityList);
|
|
162
90
|
|
|
163
|
-
//
|
|
164
|
-
for (Long id : ids) {
|
|
165
|
-
baseMapper.update(null,
|
|
166
|
-
new LambdaUpdateWrapper<Xxx>()
|
|
167
|
-
.set(Xxx::getStatus, status)
|
|
168
|
-
.eq(Xxx::getId, id));
|
|
169
|
-
}
|
|
91
|
+
// 避免:循环单个更新(N次数据库访问)
|
|
170
92
|
```
|
|
171
93
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
## 2. SQL 优化
|
|
175
|
-
|
|
176
|
-
### 慢查询分析
|
|
177
|
-
|
|
178
|
-
```sql
|
|
179
|
-
-- 开启慢查询日志(MySQL 层面)
|
|
180
|
-
SET GLOBAL slow_query_log = 'ON';
|
|
181
|
-
SET GLOBAL long_query_time = 1; -- 超过1秒记录
|
|
182
|
-
|
|
183
|
-
-- 查看慢查询配置
|
|
184
|
-
SHOW VARIABLES LIKE '%slow_query%';
|
|
185
|
-
|
|
186
|
-
-- 本项目使用 p6spy 进行 SQL 监控(非 Druid)
|
|
187
|
-
-- 开发环境默认开启:spring.datasource.dynamic.p6spy: true
|
|
188
|
-
-- 生产环境默认关闭:spring.datasource.dynamic.p6spy: false
|
|
189
|
-
-- SQL 日志输出在 ./logs/console.log 中,格式包含 Consume Time
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### 执行计划分析
|
|
193
|
-
|
|
194
|
-
```sql
|
|
195
|
-
-- 使用 EXPLAIN 分析
|
|
196
|
-
EXPLAIN SELECT * FROM sys_user WHERE dept_id = 100;
|
|
197
|
-
|
|
198
|
-
-- 关注字段
|
|
199
|
-
-- type: ALL(全表扫描) < index < range < ref < const
|
|
200
|
-
-- rows: 扫描行数,越少越好
|
|
201
|
-
-- Extra: Using filesort(需优化)、Using temporary(需优化)
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### 索引优化
|
|
205
|
-
|
|
206
|
-
```sql
|
|
207
|
-
-- ✅ 好的索引设计
|
|
208
|
-
CREATE INDEX idx_dept_status ON sys_user(dept_id, status);
|
|
209
|
-
|
|
210
|
-
-- 索引使用原则
|
|
211
|
-
-- 1. 最左前缀原则
|
|
212
|
-
-- 2. 避免在索引列上使用函数
|
|
213
|
-
-- 3. 避免 != 和 NOT IN
|
|
214
|
-
-- 4. 注意索引选择性
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### N+1 查询优化
|
|
94
|
+
### N+1 查询修复
|
|
218
95
|
|
|
219
96
|
```java
|
|
220
|
-
//
|
|
97
|
+
// 错误:循环查询
|
|
221
98
|
for (Order order : orders) {
|
|
222
|
-
User user = userMapper.selectById(order.getUserId());
|
|
99
|
+
User user = userMapper.selectById(order.getUserId());
|
|
223
100
|
}
|
|
224
101
|
|
|
225
|
-
//
|
|
226
|
-
List<Long> userIds = orders.stream()
|
|
227
|
-
.map(Order::getUserId)
|
|
228
|
-
.distinct()
|
|
229
|
-
.toList();
|
|
102
|
+
// 正确:批量查询 + Map 映射
|
|
103
|
+
List<Long> userIds = orders.stream().map(Order::getUserId).distinct().toList();
|
|
230
104
|
Map<Long, User> userMap = userMapper.selectBatchIds(userIds).stream()
|
|
231
105
|
.collect(Collectors.toMap(User::getUserId, Function.identity()));
|
|
232
|
-
|
|
233
106
|
for (Order order : orders) {
|
|
234
|
-
User user = userMap.get(order.getUserId());
|
|
107
|
+
User user = userMap.get(order.getUserId());
|
|
235
108
|
}
|
|
236
109
|
```
|
|
237
110
|
|
|
238
111
|
---
|
|
239
112
|
|
|
240
|
-
##
|
|
241
|
-
|
|
242
|
-
> **实际代码位置**:`ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java`
|
|
113
|
+
## 2. 缓存优化
|
|
243
114
|
|
|
244
|
-
|
|
115
|
+
> 源码:`ruoyi-common/ruoyi-common-redis/.../RedisUtils.java`
|
|
245
116
|
|
|
246
|
-
###
|
|
117
|
+
### RedisUtils 操作
|
|
247
118
|
|
|
248
119
|
```java
|
|
249
120
|
import org.dromara.common.redis.utils.RedisUtils;
|
|
250
|
-
import java.time.Duration;
|
|
251
121
|
|
|
252
|
-
// 设置缓存(带过期时间)
|
|
253
122
|
RedisUtils.setCacheObject("user:" + id, userVo, Duration.ofMinutes(30));
|
|
254
|
-
|
|
255
|
-
// 获取缓存
|
|
256
123
|
UserVo cached = RedisUtils.getCacheObject("user:" + id);
|
|
257
|
-
|
|
258
|
-
// 删除缓存
|
|
259
124
|
RedisUtils.deleteObject("user:" + id);
|
|
260
|
-
|
|
261
|
-
// 检查是否存在
|
|
262
125
|
boolean exists = RedisUtils.isExistsObject("user:" + id);
|
|
263
|
-
|
|
264
|
-
// 设置过期时间
|
|
265
|
-
RedisUtils.expire("user:" + id, Duration.ofHours(1));
|
|
266
126
|
```
|
|
267
127
|
|
|
268
|
-
### Spring Cache
|
|
128
|
+
### Spring Cache 注解
|
|
269
129
|
|
|
270
130
|
```java
|
|
271
|
-
// 使用 Spring Cache 注解(更简洁)
|
|
272
131
|
@Cacheable(value = "user", key = "#id")
|
|
273
|
-
public UserVo getById(Long id) {
|
|
274
|
-
return MapstructUtils.convert(baseMapper.selectById(id), UserVo.class);
|
|
275
|
-
}
|
|
132
|
+
public UserVo getById(Long id) { ... }
|
|
276
133
|
|
|
277
134
|
@CacheEvict(value = "user", key = "#bo.id")
|
|
278
|
-
public int update(UserBo bo) {
|
|
279
|
-
return baseMapper.updateById(MapstructUtils.convert(bo, User.class));
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// 缓存穿透防护(缓存 null 值)
|
|
283
|
-
@Cacheable(value = "user", key = "#id", unless = "#result == null")
|
|
284
|
-
public UserVo getById(Long id) {
|
|
285
|
-
// ...
|
|
286
|
-
}
|
|
135
|
+
public int update(UserBo bo) { ... }
|
|
287
136
|
```
|
|
288
137
|
|
|
289
|
-
>
|
|
290
|
-
> ```java
|
|
291
|
-
> // ❌ 错误
|
|
292
|
-
> return List.of("1", "2");
|
|
293
|
-
> // ✅ 正确
|
|
294
|
-
> return new ArrayList<>(List.of("1", "2"));
|
|
295
|
-
> ```
|
|
138
|
+
> **@Cacheable 禁止返回不可变集合**(`List.of()`、`Set.of()`),Redis 反序列化会失败。必须用 `new ArrayList<>(List.of(...))`。
|
|
296
139
|
|
|
297
140
|
### 分布式锁(Lock4j)
|
|
298
141
|
|
|
299
|
-
>
|
|
300
|
-
|
|
301
|
-
本项目使用 **Lock4j + Redisson** 实现分布式锁。
|
|
142
|
+
> 源码:`ruoyi-modules/ruoyi-demo/.../RedisLockController.java:47-67`
|
|
302
143
|
|
|
303
144
|
```java
|
|
304
|
-
import com.baomidou.lock.LockInfo;
|
|
305
|
-
import com.baomidou.lock.LockTemplate;
|
|
306
145
|
import com.baomidou.lock.annotation.Lock4j;
|
|
146
|
+
import com.baomidou.lock.LockTemplate;
|
|
147
|
+
import com.baomidou.lock.LockInfo;
|
|
307
148
|
import com.baomidou.lock.executor.RedissonLockExecutor;
|
|
308
149
|
|
|
309
|
-
//
|
|
150
|
+
// 注解方式(推荐)
|
|
310
151
|
@Lock4j(keys = {"#key"}, expire = 60000, acquireTimeout = 3000)
|
|
311
|
-
public void doSomething(String key) {
|
|
312
|
-
// 业务逻辑
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// 方式2:编程方式
|
|
316
|
-
@Autowired
|
|
317
|
-
private LockTemplate lockTemplate;
|
|
152
|
+
public void doSomething(String key) { ... }
|
|
318
153
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
154
|
+
// 编程方式
|
|
155
|
+
LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedissonLockExecutor.class);
|
|
156
|
+
if (null == lockInfo) {
|
|
157
|
+
throw new ServiceException("业务处理中,请稍后再试");
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
// 业务逻辑
|
|
161
|
+
} finally {
|
|
162
|
+
lockTemplate.releaseLock(lockInfo);
|
|
329
163
|
}
|
|
330
164
|
```
|
|
331
165
|
|
|
332
|
-
###
|
|
166
|
+
### 限流
|
|
333
167
|
|
|
334
168
|
```java
|
|
335
169
|
import org.redisson.api.RateType;
|
|
336
170
|
|
|
337
|
-
|
|
338
|
-
long remaining = RedisUtils.rateLimiter(
|
|
339
|
-
"api:rate:" + userId,
|
|
340
|
-
RateType.OVERALL, // 全局限流
|
|
341
|
-
100, // 允许的请求数
|
|
342
|
-
60 // 时间窗口(秒)
|
|
343
|
-
);
|
|
344
|
-
|
|
171
|
+
long remaining = RedisUtils.rateLimiter("api:rate:" + userId, RateType.OVERALL, 100, 60);
|
|
345
172
|
if (remaining < 0) {
|
|
346
|
-
throw new ServiceException("
|
|
173
|
+
throw new ServiceException("请求过于频繁");
|
|
347
174
|
}
|
|
348
175
|
```
|
|
349
176
|
|
|
@@ -353,49 +180,38 @@ if (remaining < 0) {
|
|
|
353
180
|
import com.github.benmanes.caffeine.cache.Cache;
|
|
354
181
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
355
182
|
|
|
356
|
-
// 对于热点数据,可以使用本地缓存减少 Redis 访问
|
|
357
183
|
private final Cache<Long, UserVo> localCache = Caffeine.newBuilder()
|
|
358
|
-
.maximumSize(1000)
|
|
359
|
-
.expireAfterWrite(5, TimeUnit.MINUTES)
|
|
184
|
+
.maximumSize(1000)
|
|
185
|
+
.expireAfterWrite(5, TimeUnit.MINUTES)
|
|
360
186
|
.build();
|
|
361
187
|
|
|
362
|
-
public UserVo
|
|
188
|
+
public UserVo getUser(Long id) {
|
|
363
189
|
return localCache.get(id, key -> {
|
|
364
|
-
// 本地缓存未命中,从 Redis 或数据库获取
|
|
365
190
|
UserVo cached = RedisUtils.getCacheObject("user:" + id);
|
|
366
|
-
|
|
367
|
-
return cached;
|
|
368
|
-
}
|
|
369
|
-
return MapstructUtils.convert(baseMapper.selectById(id), UserVo.class);
|
|
191
|
+
return cached != null ? cached : baseMapper.selectVoById(id);
|
|
370
192
|
});
|
|
371
193
|
}
|
|
372
194
|
```
|
|
373
195
|
|
|
374
196
|
---
|
|
375
197
|
|
|
376
|
-
##
|
|
377
|
-
|
|
378
|
-
本项目使用 `TenantEntity` 实现多租户,需要注意索引设计。
|
|
198
|
+
## 3. 多租户优化
|
|
379
199
|
|
|
380
200
|
```sql
|
|
381
|
-
--
|
|
201
|
+
-- tenant_id 必须建索引
|
|
382
202
|
CREATE INDEX idx_tenant_id ON xxx_table(tenant_id);
|
|
383
203
|
|
|
384
|
-
--
|
|
204
|
+
-- 复合索引第一列放 tenant_id(多租户自动添加 tenant_id 条件)
|
|
385
205
|
CREATE INDEX idx_tenant_status ON xxx_table(tenant_id, status);
|
|
386
206
|
CREATE INDEX idx_tenant_create_time ON xxx_table(tenant_id, create_time);
|
|
387
|
-
CREATE INDEX idx_tenant_user ON xxx_table(tenant_id, create_by);
|
|
388
|
-
|
|
389
|
-
-- ⚠️ 注意:多租户查询会自动添加 tenant_id 条件
|
|
390
|
-
-- 确保复合索引的第一列是 tenant_id
|
|
391
207
|
```
|
|
392
208
|
|
|
393
209
|
---
|
|
394
210
|
|
|
395
|
-
##
|
|
211
|
+
## 4. 批量操作优化
|
|
396
212
|
|
|
397
213
|
```java
|
|
398
|
-
//
|
|
214
|
+
// 推荐:分批处理(每批500条)
|
|
399
215
|
public void batchInsert(List<Xxx> list) {
|
|
400
216
|
int batchSize = 500;
|
|
401
217
|
for (int i = 0; i < list.size(); i += batchSize) {
|
|
@@ -404,18 +220,14 @@ public void batchInsert(List<Xxx> list) {
|
|
|
404
220
|
}
|
|
405
221
|
}
|
|
406
222
|
|
|
407
|
-
//
|
|
408
|
-
baseMapper.insertBatch(hugeList); // 可能超时或内存溢出
|
|
409
|
-
|
|
410
|
-
// ✅ 批量更新优化
|
|
223
|
+
// 批量更新同理
|
|
411
224
|
@Transactional(rollbackFor = Exception.class)
|
|
412
225
|
public void batchUpdate(List<XxxBo> list) {
|
|
413
226
|
int batchSize = 500;
|
|
414
227
|
for (int i = 0; i < list.size(); i += batchSize) {
|
|
415
228
|
int end = Math.min(i + batchSize, list.size());
|
|
416
229
|
List<Xxx> batch = list.subList(i, end).stream()
|
|
417
|
-
.map(bo -> MapstructUtils.convert(bo, Xxx.class))
|
|
418
|
-
.toList();
|
|
230
|
+
.map(bo -> MapstructUtils.convert(bo, Xxx.class)).toList();
|
|
419
231
|
Db.updateBatchById(batch);
|
|
420
232
|
}
|
|
421
233
|
}
|
|
@@ -423,205 +235,69 @@ public void batchUpdate(List<XxxBo> list) {
|
|
|
423
235
|
|
|
424
236
|
---
|
|
425
237
|
|
|
426
|
-
##
|
|
427
|
-
|
|
428
|
-
```java
|
|
429
|
-
// ❌ 不好:返回所有字段
|
|
430
|
-
public List<Order> listOrders() {
|
|
431
|
-
return baseMapper.selectList(null);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// ✅ 好的:只返回需要的字段(使用 VO)
|
|
435
|
-
public List<OrderSimpleVo> listOrders() {
|
|
436
|
-
return baseMapper.selectList(null).stream()
|
|
437
|
-
.map(o -> MapstructUtils.convert(o, OrderSimpleVo.class))
|
|
438
|
-
.toList();
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// ✅ 好的:分页查询(本项目标准写法)
|
|
442
|
-
public TableDataInfo<OrderVo> pageOrders(OrderBo bo, PageQuery pageQuery) {
|
|
443
|
-
LambdaQueryWrapper<Order> lqw = buildQueryWrapper(bo);
|
|
444
|
-
Page<OrderVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
|
445
|
-
return TableDataInfo.build(result);
|
|
446
|
-
}
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
---
|
|
450
|
-
|
|
451
|
-
## 7. 性能日志分析
|
|
452
|
-
|
|
453
|
-
> **实际代码位置**:`ruoyi-admin/src/main/resources/logback-plus.xml:52-72`
|
|
454
|
-
|
|
455
|
-
### 日志文件位置
|
|
456
|
-
|
|
457
|
-
| 环境 | 日志文件 | 说明 |
|
|
458
|
-
|------|---------|------|
|
|
459
|
-
| **开发环境** | `./logs/console.log` | 每次启动清空,包含所有日志和 SQL 日志 |
|
|
460
|
-
| 生产环境 | `./logs/sys-*.log` | 分级别按天滚动,保留60天 |
|
|
238
|
+
## 5. 性能日志分析
|
|
461
239
|
|
|
462
|
-
>
|
|
463
|
-
> - 只包含当前启动的日志,范围小,易分析
|
|
464
|
-
> - 包含完整的 SQL 执行时间和性能数据
|
|
465
|
-
> - 启动时自动清空,避免历史日志干扰
|
|
240
|
+
> 日志配置:`ruoyi-admin/src/main/resources/logback-plus.xml:52-72`
|
|
466
241
|
|
|
467
|
-
###
|
|
242
|
+
### 日志文件
|
|
468
243
|
|
|
469
|
-
|
|
244
|
+
| 环境 | 文件 | 说明 |
|
|
245
|
+
|------|------|------|
|
|
246
|
+
| **开发** | `./logs/console.log` | 每次启动清空,含 SQL 日志 |
|
|
247
|
+
| 生产 | `./logs/sys-*.log` | 按天滚动,保留60天 |
|
|
470
248
|
|
|
471
|
-
|
|
472
|
-
|---------|--------|-------------|
|
|
473
|
-
| 1. 接口响应慢 | "接口慢"、"响应时间长"、"超时" | SQL 执行时间、慢查询(>200ms)、N+1 查询 |
|
|
474
|
-
| 2. SQL 性能问题 | "SQL慢"、"查询慢"、"数据库慢" | p6spy 日志中的 Consume Time、SQL 语句 |
|
|
475
|
-
| 3. 内存或 CPU 高 | "内存占用"、"CPU高"、"卡顿" | OutOfMemoryError、GC 日志、线程池满 |
|
|
476
|
-
| 4. 频繁报错 | "一直报错"、"很多错误" | ERROR 级别日志、异常堆栈、出现频率 |
|
|
477
|
-
| 5. 启动慢 | "启动慢"、"启动时间长" | 应用启动时间、Bean 初始化耗时 |
|
|
249
|
+
### SQL 监控
|
|
478
250
|
|
|
479
|
-
|
|
251
|
+
开发环境 p6spy 默认开启:`spring.datasource.dynamic.p6spy: true`
|
|
480
252
|
|
|
253
|
+
日志格式:
|
|
481
254
|
```
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
示例1(普通日志):
|
|
485
|
-
2026-01-08 22:12:10 [req-123] [http-nio-8080-exec-1] INFO o.d.system.controller.SysUserController - 查询用户列表
|
|
486
|
-
|
|
487
|
-
示例2(SQL 日志 - p6spy):
|
|
488
|
-
2026-01-08 22:12:10 [req-123] [http-nio-8080-exec-1] INFO p6spy - Consume Time:245 ms 2026-01-08 22:12:10
|
|
255
|
+
2026-01-08 22:12:10 [req-123] [...] INFO p6spy - Consume Time:245 ms
|
|
489
256
|
Execute SQL:SELECT * FROM sys_user WHERE tenant_id = '000000' AND del_flag = '0'
|
|
490
257
|
```
|
|
491
258
|
|
|
492
|
-
###
|
|
493
|
-
|
|
494
|
-
#### 场景 1:分析慢 SQL(最常用)
|
|
259
|
+
### AI 主动读日志的触发条件
|
|
495
260
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
261
|
+
| 场景 | 关键词 | 分析重点 |
|
|
262
|
+
|------|--------|---------|
|
|
263
|
+
| 接口慢 | "接口慢"、"超时" | SQL 执行时间、N+1 查询 |
|
|
264
|
+
| SQL 慢 | "SQL慢"、"查询慢" | p6spy Consume Time |
|
|
265
|
+
| 内存/CPU | "内存高"、"卡顿" | OOM、GC、线程池满 |
|
|
266
|
+
| 频繁报错 | "一直报错" | ERROR 日志、异常频率 |
|
|
500
267
|
|
|
501
|
-
|
|
502
|
-
- 执行时间是否合理(一般 < 200ms)
|
|
503
|
-
- 是否有索引缺失(WHERE 条件的字段)
|
|
504
|
-
- 是否存在 N+1 查询(循环中重复相同 SQL)
|
|
505
|
-
- 是否查询了不必要的字段(SELECT *)
|
|
506
|
-
|
|
507
|
-
#### 场景 2:统计 SQL 执行次数(发现 N+1)
|
|
268
|
+
### 分析命令
|
|
508
269
|
|
|
509
270
|
```bash
|
|
510
|
-
#
|
|
511
|
-
grep "
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
**N+1 特征**:
|
|
515
|
-
- 同一个 SELECT 语句重复出现多次
|
|
516
|
-
- 通常在循环中执行
|
|
517
|
-
- 例如:`SELECT * FROM sys_user WHERE user_id = ?` 出现 100 次
|
|
271
|
+
# 慢 SQL(>200ms)
|
|
272
|
+
grep "Consume Time" ./logs/console.log | grep -E "Consume Time:[2-9][0-9]{2,}|[0-9]{4,}"
|
|
518
273
|
|
|
519
|
-
|
|
274
|
+
# N+1 检测(同一 SQL 重复多次)
|
|
275
|
+
grep "Execute SQL" ./logs/console.log | sed 's/WHERE.*/WHERE .../' | sort | uniq -c | sort -rn | head -20
|
|
520
276
|
|
|
521
|
-
|
|
522
|
-
# 查找所有 ERROR 日志
|
|
277
|
+
# 错误日志
|
|
523
278
|
grep "ERROR" ./logs/console.log | tail -50
|
|
524
|
-
|
|
525
|
-
# 查找超时错误
|
|
526
|
-
grep -i "timeout\|timed out" ./logs/console.log
|
|
527
|
-
|
|
528
|
-
# 查找内存溢出
|
|
529
|
-
grep -i "OutOfMemoryError\|heap space" ./logs/console.log
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### 完整分析示例
|
|
533
|
-
|
|
534
|
-
**用户问题**:"接口调用很慢,查询用户列表需要 3 秒"
|
|
535
|
-
|
|
536
|
-
**AI 分析流程**:
|
|
537
|
-
|
|
538
|
-
```bash
|
|
539
|
-
# 1. 读取日志文件
|
|
540
|
-
Read ./logs/console.log
|
|
541
|
-
|
|
542
|
-
# 2. 搜索用户相关的 SQL
|
|
543
|
-
grep "sys_user" ./logs/console.log | grep "Consume Time"
|
|
544
|
-
|
|
545
|
-
# 3. 找到慢查询
|
|
546
|
-
# 输出:Consume Time:2845 ms ... Execute SQL:SELECT * FROM sys_user WHERE status = '0'
|
|
547
|
-
|
|
548
|
-
# 4. 分析问题
|
|
549
|
-
# - 执行时间 2845ms(远超 200ms 阈值)
|
|
550
|
-
# - SELECT * 查询所有字段
|
|
551
|
-
# - WHERE status = '0' 可能缺少索引
|
|
552
|
-
|
|
553
|
-
# 5. 给出优化建议
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
**AI 输出**:
|
|
557
|
-
```
|
|
558
|
-
性能分析结果:
|
|
559
|
-
|
|
560
|
-
1. 慢查询定位
|
|
561
|
-
- SQL: SELECT * FROM sys_user WHERE status = '0'
|
|
562
|
-
- 执行时间: 2845ms(严重超标,应 < 200ms)
|
|
563
|
-
|
|
564
|
-
2. 问题原因
|
|
565
|
-
- status 字段缺少索引,导致全表扫描
|
|
566
|
-
- 查询所有字段(SELECT *),增加数据传输量
|
|
567
|
-
- 未使用缓存
|
|
568
|
-
|
|
569
|
-
3. 优化方案(按优先级)
|
|
570
|
-
✅ 立即执行:
|
|
571
|
-
- 添加索引: CREATE INDEX idx_status ON sys_user(status);
|
|
572
|
-
- 只查询需要的字段
|
|
573
|
-
|
|
574
|
-
✅ 短期优化:
|
|
575
|
-
- 添加 Redis 缓存(过期时间 5 分钟)
|
|
576
|
-
- 使用 @Cacheable 注解
|
|
577
|
-
|
|
578
|
-
4. 预期效果
|
|
579
|
-
- 添加索引后: 2845ms → 50ms(预计提升 98%)
|
|
580
279
|
```
|
|
581
280
|
|
|
582
281
|
---
|
|
583
282
|
|
|
584
|
-
##
|
|
283
|
+
## 常见问题速查
|
|
585
284
|
|
|
586
|
-
|
|
285
|
+
| 问题 | 原因 | 方案 |
|
|
286
|
+
|------|------|------|
|
|
287
|
+
| 接口响应慢 | SQL 无索引 | 添加索引,EXPLAIN 分析 |
|
|
288
|
+
| 接口响应慢 | N+1 查询 | 批量查询 + Map 映射 |
|
|
289
|
+
| 接口响应慢 | 未缓存 | RedisUtils 或 @Cacheable |
|
|
290
|
+
| 分页查询慢 | 深分页 | 游标分页或限制页码 |
|
|
291
|
+
| 批量操作超时 | 数据量太大 | 分批500条处理 |
|
|
292
|
+
| 多租户查询慢 | tenant_id 无索引 | 添加 tenant_id 复合索引 |
|
|
293
|
+
| 内存溢出 | 大数据量一次加载 | 分批/流式处理 |
|
|
587
294
|
|
|
588
|
-
|
|
589
|
-
|------|------|--------|---------|
|
|
590
|
-
| 接口响应时间 | < 200ms | > 500ms | p6spy/SkyWalking |
|
|
591
|
-
| 数据库查询 | < 50ms | > 200ms | p6spy SQL 日志 |
|
|
592
|
-
| 内存使用 | < 70% | > 85% | Arthas/JVM监控 |
|
|
593
|
-
| CPU 使用 | < 60% | > 80% | 系统监控 |
|
|
295
|
+
---
|
|
594
296
|
|
|
595
|
-
|
|
297
|
+
## 监控工具
|
|
596
298
|
|
|
597
299
|
| 工具 | 用途 | 使用方式 |
|
|
598
300
|
|------|------|---------|
|
|
599
|
-
| **p6spy** | SQL 监控、执行时间 |
|
|
301
|
+
| **p6spy** | SQL 监控、执行时间 | 开发环境默认开启 |
|
|
600
302
|
| **Arthas** | JVM 诊断、火焰图 | `java -jar arthas-boot.jar` |
|
|
601
303
|
| **SkyWalking** | 分布式链路追踪 | 需单独部署 |
|
|
602
|
-
| **JProfiler** | 内存分析、CPU分析 | IDE 插件 |
|
|
603
|
-
|
|
604
|
-
### 日志分析命令速查
|
|
605
|
-
|
|
606
|
-
| 命令 | 用途 | 示例 |
|
|
607
|
-
|------|------|------|
|
|
608
|
-
| `wc -l` | 统计行数 | `wc -l console.log` |
|
|
609
|
-
| `grep` | 搜索关键词 | `grep "ERROR" console.log` |
|
|
610
|
-
| `tail -n 100` | 查看最后 100 行 | `tail -n 100 console.log` |
|
|
611
|
-
| `grep -A 5 -B 5` | 显示匹配行的前后 5 行 | `grep -A 5 -B 5 "ERROR" console.log` |
|
|
612
|
-
| `sort \| uniq -c` | 统计重复次数 | `grep "Execute SQL" console.log \| sort \| uniq -c` |
|
|
613
|
-
|
|
614
|
-
---
|
|
615
|
-
|
|
616
|
-
## 常见性能问题速查
|
|
617
|
-
|
|
618
|
-
| 问题现象 | 可能原因 | 解决方案 |
|
|
619
|
-
|----------|----------|----------|
|
|
620
|
-
| 接口响应慢 | SQL 无索引 | 添加合适索引,使用 EXPLAIN 分析 |
|
|
621
|
-
| 接口响应慢 | N+1 查询 | 改为批量查询 + Map 映射 |
|
|
622
|
-
| 接口响应慢 | 未使用缓存 | 使用 RedisUtils 或 @Cacheable |
|
|
623
|
-
| 分页查询慢 | 深分页问题 | 使用游标分页或限制页码范围 |
|
|
624
|
-
| 批量操作超时 | 一次操作太多数据 | 分批处理,每批 500 条 |
|
|
625
|
-
| 多租户查询慢 | tenant_id 无索引 | 添加 tenant_id 复合索引 |
|
|
626
|
-
| 频繁请求 | 无防抖/缓存 | 添加缓存或接口限流 |
|
|
627
|
-
| 内存溢出 | 大数据量一次加载 | 分批处理、流式处理 |
|