ai-engineering-init 1.3.3 → 1.4.0
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 +4 -1
- package/.claude/settings.json +3 -3
- package/.claude/skills/add-skill/SKILL.md +252 -116
- 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 -325
- package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +328 -0
- 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 +252 -116
- 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 -325
- package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +328 -0
- 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.json +3 -3
- package/.cursor/rules/skill-activation.mdc +2 -0
- package/.cursor/skills/add-skill/SKILL.md +252 -116
- 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 -325
- package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +328 -0
- 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/AGENTS.md +1 -0
- package/package.json +1 -1
|
@@ -25,91 +25,45 @@ description: |
|
|
|
25
25
|
|
|
26
26
|
## 1. Sa-Token 认证授权
|
|
27
27
|
|
|
28
|
-
### 1.1
|
|
28
|
+
### 1.1 权限注解
|
|
29
29
|
|
|
30
30
|
```java
|
|
31
31
|
import cn.dev33.satoken.annotation.*;
|
|
32
|
-
import cn.dev33.satoken.stp.StpUtil;
|
|
33
32
|
|
|
34
|
-
// 登录校验
|
|
35
|
-
@
|
|
36
|
-
@
|
|
37
|
-
|
|
33
|
+
@SaCheckLogin // 登录校验
|
|
34
|
+
@SaCheckPermission("system:user:add") // 权限校验
|
|
35
|
+
@SaCheckRole("admin") // 角色校验
|
|
36
|
+
@SaCheckSafe // 二级认证(敏感操作)
|
|
38
37
|
|
|
39
|
-
//
|
|
40
|
-
@SaCheckPermission("system:user:add")
|
|
41
|
-
@PostMapping("/addUser")
|
|
42
|
-
public R<Long> addUser(@RequestBody UserBo bo) { }
|
|
43
|
-
|
|
44
|
-
// 角色校验
|
|
45
|
-
@SaCheckRole("admin")
|
|
46
|
-
@DeleteMapping("/deleteUser/{id}")
|
|
47
|
-
public R<Void> deleteUser(@PathVariable Long id) { }
|
|
48
|
-
|
|
49
|
-
// 多权限校验(满足其一)
|
|
38
|
+
// 多权限(满足其一 / 全部满足)
|
|
50
39
|
@SaCheckPermission(value = {"system:user:add", "system:user:update"}, mode = SaMode.OR)
|
|
51
|
-
|
|
52
|
-
// 多权限校验(全部满足)
|
|
53
40
|
@SaCheckPermission(value = {"system:user:add", "system:user:update"}, mode = SaMode.AND)
|
|
54
41
|
|
|
55
|
-
//
|
|
42
|
+
// 多角色(满足其一)
|
|
56
43
|
@SaCheckRole(value = {"admin", "editor"}, mode = SaMode.OR)
|
|
57
|
-
|
|
58
|
-
// 二级认证(敏感操作需要再次验证)
|
|
59
|
-
@SaCheckSafe
|
|
60
44
|
```
|
|
61
45
|
|
|
62
|
-
### 1.2 LoginHelper
|
|
46
|
+
### 1.2 LoginHelper 工具类
|
|
63
47
|
|
|
64
|
-
>
|
|
48
|
+
> 位置:`ruoyi-common-satoken/.../utils/LoginHelper.java`
|
|
65
49
|
|
|
66
50
|
```java
|
|
67
51
|
import org.dromara.common.satoken.utils.LoginHelper;
|
|
68
52
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
// 获取当前登录用户完整信息
|
|
53
|
+
// 用户信息
|
|
72
54
|
LoginUser user = LoginHelper.getLoginUser();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Long deptId = LoginHelper.getDeptId();
|
|
86
|
-
String deptName = LoginHelper.getDeptName();
|
|
87
|
-
String deptCategory = LoginHelper.getDeptCategory();
|
|
88
|
-
|
|
89
|
-
// 根据 Token 获取用户信息
|
|
90
|
-
LoginUser user = LoginHelper.getLoginUser(token);
|
|
91
|
-
|
|
92
|
-
// ==================== 管理员判断 ====================
|
|
93
|
-
|
|
94
|
-
// 判断是否为超级管理员(userId = 1)
|
|
95
|
-
boolean isSuperAdmin = LoginHelper.isSuperAdmin();
|
|
96
|
-
boolean isSuperAdmin = LoginHelper.isSuperAdmin(userId);
|
|
97
|
-
|
|
98
|
-
// 判断是否为租户管理员
|
|
99
|
-
boolean isTenantAdmin = LoginHelper.isTenantAdmin();
|
|
100
|
-
boolean isTenantAdmin = LoginHelper.isTenantAdmin(rolePermissions);
|
|
101
|
-
|
|
102
|
-
// 检查是否已登录
|
|
103
|
-
boolean isLogin = LoginHelper.isLogin();
|
|
104
|
-
|
|
105
|
-
// ==================== 用户类型 ====================
|
|
106
|
-
|
|
107
|
-
// 获取用户类型(PC端用户、APP用户等)
|
|
108
|
-
UserType userType = LoginHelper.getUserType();
|
|
109
|
-
|
|
110
|
-
// ==================== 用户登录 ====================
|
|
111
|
-
|
|
112
|
-
// 用户登录(支持多设备类型)
|
|
55
|
+
Long userId = LoginHelper.getUserId();
|
|
56
|
+
String name = LoginHelper.getUsername();
|
|
57
|
+
String tenant = LoginHelper.getTenantId();
|
|
58
|
+
Long deptId = LoginHelper.getDeptId();
|
|
59
|
+
|
|
60
|
+
// 管理员判断
|
|
61
|
+
LoginHelper.isSuperAdmin(); // userId = 1
|
|
62
|
+
LoginHelper.isTenantAdmin(); // 租户管理员
|
|
63
|
+
LoginHelper.isLogin(); // 是否已登录
|
|
64
|
+
|
|
65
|
+
// 用户类型 & 登录
|
|
66
|
+
UserType type = LoginHelper.getUserType();
|
|
113
67
|
LoginHelper.login(loginUser, loginParameter);
|
|
114
68
|
```
|
|
115
69
|
|
|
@@ -119,309 +73,144 @@ LoginHelper.login(loginUser, loginParameter);
|
|
|
119
73
|
|------|-----|------|
|
|
120
74
|
| 超级管理员角色 | `superadmin` | 拥有所有权限 |
|
|
121
75
|
| 租户管理员角色 | `admin` | 租户内所有权限 |
|
|
122
|
-
| 通配符权限 | `*:*:*` |
|
|
76
|
+
| 通配符权限 | `*:*:*` | 所有权限标识 |
|
|
123
77
|
| 超级管理员ID | `1L` | 系统超管用户ID |
|
|
124
78
|
|
|
125
79
|
---
|
|
126
80
|
|
|
127
81
|
## 2. 数据脱敏(@Sensitive)
|
|
128
82
|
|
|
129
|
-
>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
| 策略 | 枚举值 | 脱敏效果 | 说明 |
|
|
134
|
-
|------|--------|---------|------|
|
|
135
|
-
| 身份证 | `ID_CARD` | `110***********1234` | 保留前3位和后4位 |
|
|
136
|
-
| 手机号 | `PHONE` | `138****8888` | 保留前3位和后4位 |
|
|
137
|
-
| 邮箱 | `EMAIL` | `t**@example.com` | 保留用户名首尾和完整域名 |
|
|
138
|
-
| 银行卡 | `BANK_CARD` | `6222***********1234` | 保留前4位和后4位 |
|
|
139
|
-
| 中文姓名 | `CHINESE_NAME` | `张*` | 保留姓氏,名字用*代替 |
|
|
140
|
-
| 地址 | `ADDRESS` | `北京市朝阳区****` | 保留前8个字符 |
|
|
141
|
-
| 固定电话 | `FIXED_PHONE` | `010****1234` | 保留区号和后4位 |
|
|
142
|
-
| 密码 | `PASSWORD` | `******` | 全部用*代替 |
|
|
143
|
-
| IPv4 地址 | `IPV4` | `192.168.***.***` | 保留网络段,隐藏主机段 |
|
|
144
|
-
| IPv6 地址 | `IPV6` | 部分隐藏 | 保留前缀,隐藏接口标识 |
|
|
145
|
-
| 车牌号 | `CAR_LICENSE` | `京A***12` | 支持普通和新能源车辆 |
|
|
146
|
-
| 用户ID | `USER_ID` | 随机数字 | 生成随机数字替代 |
|
|
147
|
-
| 首字符保留 | `FIRST_MASK` | `张***` | 只显示第一个字符 |
|
|
148
|
-
| 通用掩码 | `STRING_MASK` | `1234**7890` | 前4位+4个*+后4位 |
|
|
149
|
-
| 高安全级别 | `MASK_HIGH_SECURITY` | `to******en` | Token/私钥脱敏,前2后2可见 |
|
|
150
|
-
| 清空 | `CLEAR` | 空字符串 | 返回空字符串 |
|
|
151
|
-
| 置空 | `CLEAR_TO_NULL` | `null` | 返回 null |
|
|
152
|
-
|
|
153
|
-
### 2.2 基本用法
|
|
83
|
+
> 位置:`ruoyi-common-sensitive/.../`
|
|
84
|
+
> 完整 17 种策略详见 `references/sensitive-strategies.md`
|
|
85
|
+
|
|
86
|
+
### 基本用法
|
|
154
87
|
|
|
155
88
|
```java
|
|
156
89
|
import org.dromara.common.sensitive.annotation.Sensitive;
|
|
157
90
|
import org.dromara.common.sensitive.core.SensitiveStrategy;
|
|
158
91
|
|
|
159
92
|
public class UserVo {
|
|
93
|
+
@Sensitive(strategy = SensitiveStrategy.PHONE) // 138****8888
|
|
94
|
+
private String phone;
|
|
160
95
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
private String phone; // 138****8888
|
|
164
|
-
|
|
165
|
-
// 身份证脱敏
|
|
166
|
-
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
|
|
167
|
-
private String idCard; // 110***********1234
|
|
168
|
-
|
|
169
|
-
// 邮箱脱敏
|
|
170
|
-
@Sensitive(strategy = SensitiveStrategy.EMAIL)
|
|
171
|
-
private String email; // t**@example.com
|
|
96
|
+
@Sensitive(strategy = SensitiveStrategy.ID_CARD) // 110***********1234
|
|
97
|
+
private String idCard;
|
|
172
98
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
private String bankCard; // 6222***********1234
|
|
99
|
+
@Sensitive(strategy = SensitiveStrategy.EMAIL) // t**@example.com
|
|
100
|
+
private String email;
|
|
176
101
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
private String realName; // 张*
|
|
102
|
+
@Sensitive(strategy = SensitiveStrategy.BANK_CARD) // 6222***********1234
|
|
103
|
+
private String bankCard;
|
|
180
104
|
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
private String address; // 北京市朝阳区****
|
|
105
|
+
@Sensitive(strategy = SensitiveStrategy.CHINESE_NAME) // 张*
|
|
106
|
+
private String realName;
|
|
184
107
|
|
|
185
|
-
//
|
|
186
|
-
@Sensitive(strategy = SensitiveStrategy.PASSWORD)
|
|
108
|
+
@Sensitive(strategy = SensitiveStrategy.PASSWORD) // ******
|
|
187
109
|
private String password;
|
|
188
|
-
|
|
189
|
-
// IP 地址脱敏
|
|
190
|
-
@Sensitive(strategy = SensitiveStrategy.IPV4)
|
|
191
|
-
private String loginIp; // 192.168.***.***
|
|
192
|
-
|
|
193
|
-
// 车牌号脱敏
|
|
194
|
-
@Sensitive(strategy = SensitiveStrategy.CAR_LICENSE)
|
|
195
|
-
private String carNumber; // 京A***12
|
|
196
110
|
}
|
|
197
111
|
```
|
|
198
112
|
|
|
199
|
-
###
|
|
113
|
+
### 基于角色/权限的脱敏控制
|
|
200
114
|
|
|
201
115
|
```java
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
@Sensitive(strategy = SensitiveStrategy.BANK_CARD,
|
|
215
|
-
roleKey = {"admin"},
|
|
216
|
-
perms = {"finance:account:query"})
|
|
217
|
-
private String bankCard;
|
|
218
|
-
|
|
219
|
-
// 多角色(任一角色即可)
|
|
220
|
-
@Sensitive(strategy = SensitiveStrategy.EMAIL,
|
|
221
|
-
roleKey = {"admin", "finance"})
|
|
222
|
-
private String email;
|
|
223
|
-
|
|
224
|
-
// 多权限(任一权限即可)
|
|
225
|
-
@Sensitive(strategy = SensitiveStrategy.ADDRESS,
|
|
226
|
-
perms = {"system:user:detail", "system:user:update"})
|
|
227
|
-
private String address;
|
|
228
|
-
}
|
|
116
|
+
// admin 角色可查看原数据,其他用户看脱敏数据
|
|
117
|
+
@Sensitive(strategy = SensitiveStrategy.ID_CARD, roleKey = {"admin"})
|
|
118
|
+
private String idCard;
|
|
119
|
+
|
|
120
|
+
// 需要权限才能看原数据
|
|
121
|
+
@Sensitive(strategy = SensitiveStrategy.PHONE, perms = {"system:user:detail"})
|
|
122
|
+
private String phone;
|
|
123
|
+
|
|
124
|
+
// roleKey 和 perms 是 OR 关系
|
|
125
|
+
@Sensitive(strategy = SensitiveStrategy.BANK_CARD,
|
|
126
|
+
roleKey = {"admin"}, perms = {"finance:account:query"})
|
|
127
|
+
private String bankCard;
|
|
229
128
|
```
|
|
230
129
|
|
|
231
|
-
###
|
|
130
|
+
### 日志脱敏
|
|
232
131
|
|
|
233
132
|
```java
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
log.info("用户登录,手机号: {}", phone);
|
|
238
|
-
|
|
239
|
-
// ✅ 好的:脱敏后记录
|
|
240
|
-
log.info("用户登录,手机号: {}", DesensitizedUtil.mobilePhone(phone));
|
|
241
|
-
log.info("用户登录,身份证: {}", DesensitizedUtil.idCardNum(idCard, 3, 4));
|
|
133
|
+
// NG: log.info("手机号: {}", phone);
|
|
134
|
+
// OK:
|
|
135
|
+
log.info("手机号: {}", DesensitizedUtil.mobilePhone(phone));
|
|
242
136
|
```
|
|
243
137
|
|
|
244
138
|
---
|
|
245
139
|
|
|
246
140
|
## 3. 数据加密(@EncryptField / @ApiEncrypt)
|
|
247
141
|
|
|
248
|
-
>
|
|
142
|
+
> 位置:`ruoyi-common-encrypt/.../`
|
|
143
|
+
> 完整加密配置和工具类详见 `references/encrypt-config.md`
|
|
249
144
|
|
|
250
|
-
###
|
|
145
|
+
### 支持算法
|
|
251
146
|
|
|
252
|
-
| 算法 |
|
|
253
|
-
|
|
254
|
-
| BASE64 |
|
|
255
|
-
| AES |
|
|
256
|
-
| RSA |
|
|
257
|
-
| SM2 |
|
|
258
|
-
| SM4 |
|
|
147
|
+
| 算法 | 类型 | 密钥要求 |
|
|
148
|
+
|------|------|---------|
|
|
149
|
+
| BASE64 | 编码 | 无 |
|
|
150
|
+
| AES | 对称加密 | 16/24/32 位 |
|
|
151
|
+
| RSA | 非对称加密 | 公钥/私钥 |
|
|
152
|
+
| SM2 | 国密非对称 | 公钥/私钥 |
|
|
153
|
+
| SM4 | 国密对称 | 16 位 |
|
|
259
154
|
|
|
260
|
-
###
|
|
155
|
+
### 字段级加密
|
|
261
156
|
|
|
262
157
|
```java
|
|
263
158
|
import org.dromara.common.encrypt.annotation.EncryptField;
|
|
264
159
|
import org.dromara.common.encrypt.enumd.AlgorithmType;
|
|
265
|
-
import org.dromara.common.encrypt.enumd.EncodeType;
|
|
266
160
|
|
|
267
161
|
public class User {
|
|
268
|
-
|
|
269
|
-
// 默认加密(使用全局配置)
|
|
270
|
-
@EncryptField
|
|
162
|
+
@EncryptField // 默认(全局配置)
|
|
271
163
|
private String password;
|
|
272
164
|
|
|
273
|
-
|
|
274
|
-
@EncryptField(algorithm = AlgorithmType.AES)
|
|
165
|
+
@EncryptField(algorithm = AlgorithmType.AES) // AES
|
|
275
166
|
private String idCard;
|
|
276
167
|
|
|
277
|
-
|
|
278
|
-
@EncryptField(algorithm = AlgorithmType.SM4)
|
|
168
|
+
@EncryptField(algorithm = AlgorithmType.SM4) // SM4 国密
|
|
279
169
|
private String phone;
|
|
280
|
-
|
|
281
|
-
// 指定编码方式
|
|
282
|
-
@EncryptField(algorithm = AlgorithmType.AES, encode = EncodeType.HEX)
|
|
283
|
-
private String bankCard;
|
|
284
170
|
}
|
|
285
171
|
```
|
|
286
172
|
|
|
287
|
-
###
|
|
173
|
+
### API 级加密
|
|
288
174
|
|
|
289
175
|
```java
|
|
290
176
|
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
|
291
177
|
|
|
292
|
-
@
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// 请求体加密(AES+RSA 混合加密)
|
|
296
|
-
@ApiEncrypt
|
|
297
|
-
@PostMapping("/addUser")
|
|
298
|
-
public R<Long> addUser(@RequestBody UserBo bo) {
|
|
299
|
-
// 请求体会自动解密
|
|
300
|
-
return R.ok(userService.add(bo));
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// 请求解密 + 响应加密
|
|
304
|
-
@ApiEncrypt(response = true)
|
|
305
|
-
@PostMapping("/updateUser")
|
|
306
|
-
public R<Void> updateUser(@RequestBody UserBo bo) {
|
|
307
|
-
// 请求体自动解密,响应体自动加密
|
|
308
|
-
return R.status(userService.update(bo));
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
### 3.4 EncryptUtils 工具类
|
|
314
|
-
|
|
315
|
-
```java
|
|
316
|
-
import org.dromara.common.encrypt.utils.EncryptUtils;
|
|
317
|
-
|
|
318
|
-
// AES 加密/解密
|
|
319
|
-
String encrypted = EncryptUtils.encryptByAes(data, aesKey);
|
|
320
|
-
String decrypted = EncryptUtils.decryptByAes(encrypted, aesKey);
|
|
321
|
-
|
|
322
|
-
// RSA 加密/解密(公钥加密,私钥解密)
|
|
323
|
-
String encrypted = EncryptUtils.encryptByRsa(data, publicKey);
|
|
324
|
-
String decrypted = EncryptUtils.decryptByRsa(encrypted, privateKey);
|
|
325
|
-
|
|
326
|
-
// SM2 国密加密/解密
|
|
327
|
-
String encrypted = EncryptUtils.encryptBySm2(data, publicKey);
|
|
328
|
-
String decrypted = EncryptUtils.decryptBySm2(encrypted, privateKey);
|
|
329
|
-
|
|
330
|
-
// SM4 国密加密/解密
|
|
331
|
-
String encrypted = EncryptUtils.encryptBySm4(data, sm4Key);
|
|
332
|
-
String decrypted = EncryptUtils.decryptBySm4(encrypted, sm4Key);
|
|
333
|
-
|
|
334
|
-
// BASE64 编码/解码
|
|
335
|
-
String encoded = EncryptUtils.encryptByBase64(data);
|
|
336
|
-
String decoded = EncryptUtils.decryptByBase64(encoded);
|
|
337
|
-
```
|
|
178
|
+
@ApiEncrypt // 请求体自动解密
|
|
179
|
+
@PostMapping("/addUser")
|
|
180
|
+
public R<Long> addUser(@RequestBody UserBo bo) { }
|
|
338
181
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
# application.yml
|
|
343
|
-
mybatis-encryptor:
|
|
344
|
-
# 是否启用加密
|
|
345
|
-
enable: true
|
|
346
|
-
# 默认加密算法
|
|
347
|
-
algorithm: AES
|
|
348
|
-
# AES 密钥(16/24/32位)
|
|
349
|
-
password: your-aes-key-16bit
|
|
350
|
-
# 编码方式
|
|
351
|
-
encode: BASE64
|
|
352
|
-
# RSA 公钥
|
|
353
|
-
publicKey: xxx
|
|
354
|
-
# RSA 私钥
|
|
355
|
-
privateKey: xxx
|
|
182
|
+
@ApiEncrypt(response = true) // 请求解密 + 响应加密
|
|
183
|
+
@PostMapping("/updateUser")
|
|
184
|
+
public R<Void> updateUser(@RequestBody UserBo bo) { }
|
|
356
185
|
```
|
|
357
186
|
|
|
358
187
|
---
|
|
359
188
|
|
|
360
189
|
## 4. 接口限流(@RateLimiter)
|
|
361
190
|
|
|
362
|
-
>
|
|
363
|
-
|
|
364
|
-
### 4.1 限流类型
|
|
365
|
-
|
|
366
|
-
| 类型 | 枚举值 | 说明 |
|
|
367
|
-
|------|--------|------|
|
|
368
|
-
| 全局限流 | `DEFAULT` | 所有请求共享配额 |
|
|
369
|
-
| IP 限流 | `IP` | 每个 IP 独立计算 |
|
|
370
|
-
| 集群限流 | `CLUSTER` | 每个集群节点独立 |
|
|
371
|
-
|
|
372
|
-
### 4.2 使用示例
|
|
191
|
+
> 位置:`ruoyi-common-ratelimiter/.../`
|
|
373
192
|
|
|
374
193
|
```java
|
|
375
194
|
import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
|
376
195
|
import org.dromara.common.ratelimiter.enums.LimitType;
|
|
377
196
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
// 基本用法:60秒内最多100次
|
|
382
|
-
@RateLimiter(time = 60, count = 100)
|
|
383
|
-
@GetMapping("/list")
|
|
384
|
-
public R<List<XxxVo>> list() { }
|
|
385
|
-
|
|
386
|
-
// IP 限流:每个 IP 每分钟最多10次
|
|
387
|
-
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
|
388
|
-
@PostMapping("/login")
|
|
389
|
-
public R<String> login() { }
|
|
390
|
-
|
|
391
|
-
// 动态 key:基于用户ID限流
|
|
392
|
-
@RateLimiter(key = "#userId", time = 60, count = 5)
|
|
393
|
-
@PostMapping("/submit")
|
|
394
|
-
public R<Void> submit(Long userId) { }
|
|
395
|
-
|
|
396
|
-
// SpEL 表达式
|
|
397
|
-
@RateLimiter(key = "#{#user.id + ':' + #action}", time = 60, count = 5)
|
|
398
|
-
@PostMapping("/action")
|
|
399
|
-
public R<Void> doAction(User user, String action) { }
|
|
400
|
-
|
|
401
|
-
// 自定义错误消息
|
|
402
|
-
@RateLimiter(time = 60, count = 10, message = "访问过于频繁,请稍后再试")
|
|
403
|
-
@GetMapping("/data")
|
|
404
|
-
public R<DataVo> getData() { }
|
|
405
|
-
|
|
406
|
-
// 集群限流
|
|
407
|
-
@RateLimiter(time = 60, count = 1000, limitType = LimitType.CLUSTER)
|
|
408
|
-
@GetMapping("/global")
|
|
409
|
-
public R<Void> globalApi() { }
|
|
410
|
-
}
|
|
411
|
-
```
|
|
197
|
+
// 全局限流:60秒内最多100次
|
|
198
|
+
@RateLimiter(time = 60, count = 100)
|
|
412
199
|
|
|
413
|
-
|
|
200
|
+
// IP 限流:每个 IP 每分钟最多10次
|
|
201
|
+
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
|
414
202
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
| `key` | String | `""` | 限流 key,支持 SpEL 表达式 |
|
|
418
|
-
| `time` | int | `60` | 时间窗口(秒) |
|
|
419
|
-
| `count` | int | `100` | 最大请求次数 |
|
|
420
|
-
| `limitType` | LimitType | `DEFAULT` | 限流类型 |
|
|
421
|
-
| `message` | String | 国际化 key | 错误提示消息 |
|
|
422
|
-
| `timeout` | int | `86400` | Redis 超时时间(秒) |
|
|
203
|
+
// 动态 key(SpEL)
|
|
204
|
+
@RateLimiter(key = "#userId", time = 60, count = 5)
|
|
423
205
|
|
|
424
|
-
|
|
206
|
+
// 自定义错误消息
|
|
207
|
+
@RateLimiter(time = 60, count = 10, message = "访问过于频繁,请稍后再试")
|
|
208
|
+
|
|
209
|
+
// 集群限流
|
|
210
|
+
@RateLimiter(time = 60, count = 1000, limitType = LimitType.CLUSTER)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 推荐配置
|
|
425
214
|
|
|
426
215
|
| 场景 | time | count | limitType |
|
|
427
216
|
|------|------|-------|-----------|
|
|
@@ -435,48 +224,15 @@ public class ApiController {
|
|
|
435
224
|
|
|
436
225
|
## 5. 防重复提交(@RepeatSubmit)
|
|
437
226
|
|
|
438
|
-
>
|
|
439
|
-
|
|
440
|
-
### 5.1 使用示例
|
|
227
|
+
> 位置:`ruoyi-common-idempotent/.../`
|
|
441
228
|
|
|
442
229
|
```java
|
|
443
230
|
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
|
444
|
-
import java.util.concurrent.TimeUnit;
|
|
445
|
-
|
|
446
|
-
@RestController
|
|
447
|
-
public class OrderController {
|
|
448
|
-
|
|
449
|
-
// 默认:5秒内不能重复提交
|
|
450
|
-
@RepeatSubmit()
|
|
451
|
-
@PostMapping("/addOrder")
|
|
452
|
-
public R<Long> addOrder(@RequestBody OrderBo bo) { }
|
|
453
|
-
|
|
454
|
-
// 自定义间隔:10秒(毫秒单位)
|
|
455
|
-
@RepeatSubmit(interval = 10000)
|
|
456
|
-
@PostMapping("/pay")
|
|
457
|
-
public R<Void> pay(@RequestBody PayBo bo) { }
|
|
458
|
-
|
|
459
|
-
// 使用秒作为单位
|
|
460
|
-
@RepeatSubmit(interval = 10, timeUnit = TimeUnit.SECONDS)
|
|
461
|
-
@PostMapping("/submit")
|
|
462
|
-
public R<Void> submit(@RequestBody SubmitBo bo) { }
|
|
463
|
-
|
|
464
|
-
// 自定义提示消息
|
|
465
|
-
@RepeatSubmit(interval = 5000, message = "请勿重复提交订单")
|
|
466
|
-
@PostMapping("/createOrder")
|
|
467
|
-
public R<Long> createOrder(@RequestBody OrderBo bo) { }
|
|
468
|
-
}
|
|
469
|
-
```
|
|
470
231
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
| `interval` | int | `5000` | 间隔时间 |
|
|
476
|
-
| `timeUnit` | TimeUnit | `MILLISECONDS` | 时间单位 |
|
|
477
|
-
| `message` | String | 国际化 key | 错误提示消息 |
|
|
478
|
-
|
|
479
|
-
### 5.3 推荐配置
|
|
232
|
+
@RepeatSubmit() // 默认 5 秒
|
|
233
|
+
@RepeatSubmit(interval = 10, timeUnit = TimeUnit.SECONDS) // 10 秒
|
|
234
|
+
@RepeatSubmit(interval = 5000, message = "请勿重复提交订单") // 自定义消息
|
|
235
|
+
```
|
|
480
236
|
|
|
481
237
|
| 场景 | 推荐间隔 |
|
|
482
238
|
|------|---------|
|
|
@@ -489,220 +245,78 @@ public class OrderController {
|
|
|
489
245
|
|
|
490
246
|
## 6. 数据权限(@DataPermission)
|
|
491
247
|
|
|
492
|
-
>
|
|
493
|
-
|
|
494
|
-
数据权限是行级数据过滤机制,通过 MyBatis 拦截器自动注入 WHERE 条件。
|
|
495
|
-
|
|
496
|
-
### 快速示例
|
|
248
|
+
> **完整指南请使用 `data-permission` 技能**
|
|
497
249
|
|
|
498
250
|
```java
|
|
499
251
|
@DataPermission({
|
|
500
252
|
@DataColumn(key = "deptName", value = "create_dept"),
|
|
501
253
|
@DataColumn(key = "userName", value = "create_by")
|
|
502
254
|
})
|
|
503
|
-
|
|
504
|
-
public TableDataInfo<OrderVo> pageWithPermission(OrderBo bo, PageQuery pageQuery) {
|
|
505
|
-
return page(buildQueryWrapper(bo), pageQuery).convert(OrderVo.class);
|
|
506
|
-
}
|
|
255
|
+
public TableDataInfo<OrderVo> pageWithPermission(OrderBo bo, PageQuery pageQuery) { }
|
|
507
256
|
```
|
|
508
257
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
| 类型 | 说明 |
|
|
512
|
-
|------|------|
|
|
513
|
-
| 全部数据 | 无过滤 |
|
|
514
|
-
| 本部门 | 只看本部门 |
|
|
515
|
-
| 本部门及以下 | 本部门 + 子部门 |
|
|
516
|
-
| 仅本人 | 只看自己创建的 |
|
|
517
|
-
| 自定义 | 按角色关联的部门 |
|
|
518
|
-
|
|
519
|
-
**更多内容**(表别名配置、临时忽略、扩展自定义类型、问题排查)请使用 `data-permission` 技能。
|
|
258
|
+
权限类型:全部数据 | 本部门 | 本部门及以下 | 仅本人 | 自定义
|
|
520
259
|
|
|
521
260
|
---
|
|
522
261
|
|
|
523
262
|
## 7. 输入校验
|
|
524
263
|
|
|
525
264
|
```java
|
|
526
|
-
import jakarta.validation.constraints.*;
|
|
527
265
|
import org.dromara.common.core.validate.AddGroup;
|
|
528
266
|
import org.dromara.common.core.validate.EditGroup;
|
|
529
267
|
|
|
530
268
|
public class UserBo {
|
|
531
|
-
|
|
532
269
|
@NotNull(message = "ID不能为空", groups = { EditGroup.class })
|
|
533
270
|
private Long id;
|
|
534
271
|
|
|
535
272
|
@NotBlank(message = "用户名不能为空", groups = { AddGroup.class, EditGroup.class })
|
|
536
|
-
@Size(min = 2, max = 20
|
|
537
|
-
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "
|
|
273
|
+
@Size(min = 2, max = 20)
|
|
274
|
+
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "只能包含字母数字下划线")
|
|
538
275
|
private String username;
|
|
539
|
-
|
|
540
|
-
@NotBlank(message = "密码不能为空", groups = { AddGroup.class })
|
|
541
|
-
@Size(min = 6, max = 20, message = "密码长度6-20")
|
|
542
|
-
private String password;
|
|
543
|
-
|
|
544
|
-
@Email(message = "邮箱格式不正确")
|
|
545
|
-
private String email;
|
|
546
|
-
|
|
547
|
-
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
|
548
|
-
private String phone;
|
|
549
276
|
}
|
|
550
277
|
|
|
551
|
-
// Controller
|
|
552
|
-
@PostMapping(
|
|
553
|
-
public R<
|
|
554
|
-
|
|
555
|
-
@PutMapping("/updateUser")
|
|
556
|
-
public R<Void> updateUser(@Validated(EditGroup.class) @RequestBody UserBo bo) { }
|
|
278
|
+
// Controller 分组校验
|
|
279
|
+
@PostMapping public R<Long> add(@Validated(AddGroup.class) @RequestBody UserBo bo) { }
|
|
280
|
+
@PutMapping public R<Void> update(@Validated(EditGroup.class) @RequestBody UserBo bo) { }
|
|
557
281
|
```
|
|
558
282
|
|
|
559
283
|
---
|
|
560
284
|
|
|
561
285
|
## 8. 常见漏洞防护
|
|
562
286
|
|
|
563
|
-
###
|
|
287
|
+
### SQL 注入
|
|
564
288
|
|
|
565
289
|
```java
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
//
|
|
570
|
-
PlusLambdaQuery<User> query = PlusLambdaQuery.of();
|
|
571
|
-
query.eq(User::getName, name);
|
|
572
|
-
|
|
573
|
-
// ✅ 安全:MyBatis 参数绑定
|
|
574
|
-
@Select("SELECT * FROM user WHERE name = #{name}")
|
|
575
|
-
User getByName(@Param("name") String name);
|
|
576
|
-
|
|
577
|
-
// ❌ 危险:MyBatis 使用 ${}
|
|
578
|
-
@Select("SELECT * FROM user WHERE name = '${name}'") // 禁止!
|
|
579
|
-
|
|
580
|
-
// ✅ 安全:MyBatis 使用 #{}
|
|
581
|
-
@Select("SELECT * FROM user WHERE name = #{name}")
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
### 8.2 XSS 攻击防护
|
|
585
|
-
|
|
586
|
-
```java
|
|
587
|
-
import cn.hutool.http.HtmlUtil;
|
|
588
|
-
|
|
589
|
-
// 输出时转义 HTML 标签
|
|
590
|
-
String safe = HtmlUtil.escape(userInput);
|
|
591
|
-
|
|
592
|
-
// 过滤 HTML 标签(移除所有标签)
|
|
593
|
-
String text = HtmlUtil.cleanHtmlTag(userInput);
|
|
594
|
-
|
|
595
|
-
// 移除危险标签(保留安全标签)
|
|
596
|
-
String filtered = HtmlUtil.removeHtmlTag(userInput, "script", "iframe", "object");
|
|
290
|
+
// NG: "SELECT * FROM user WHERE name = '" + name + "'"
|
|
291
|
+
// NG: @Select("SELECT * FROM user WHERE name = '${name}'")
|
|
292
|
+
// OK: MyBatis-Plus LambdaQueryWrapper
|
|
293
|
+
// OK: @Select("SELECT * FROM user WHERE name = #{name}")
|
|
597
294
|
```
|
|
598
295
|
|
|
599
|
-
###
|
|
296
|
+
### 越权访问
|
|
600
297
|
|
|
601
298
|
```java
|
|
602
|
-
// ✅ 必须校验数据归属(三层架构:Service 直接调用 Mapper)
|
|
603
299
|
@Override
|
|
604
300
|
public OrderVo selectById(Long id) {
|
|
605
301
|
Order order = baseMapper.selectById(id);
|
|
606
302
|
if (ObjectUtil.isNull(order)) {
|
|
607
303
|
throw new ServiceException("订单不存在");
|
|
608
304
|
}
|
|
609
|
-
|
|
610
|
-
// 超管可以查看所有数据
|
|
611
|
-
if (LoginHelper.isSuperAdmin()) {
|
|
612
|
-
return MapstructUtils.convert(order, OrderVo.class);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// 校验数据归属
|
|
616
|
-
if (!order.getUserId().equals(LoginHelper.getUserId())) {
|
|
305
|
+
if (!LoginHelper.isSuperAdmin() && !order.getUserId().equals(LoginHelper.getUserId())) {
|
|
617
306
|
throw new ServiceException("无权访问此订单");
|
|
618
307
|
}
|
|
619
|
-
|
|
620
308
|
return MapstructUtils.convert(order, OrderVo.class);
|
|
621
309
|
}
|
|
622
310
|
|
|
623
|
-
//
|
|
624
|
-
@Override
|
|
625
|
-
@Transactional(rollbackFor = Exception.class)
|
|
626
|
-
public int deleteByIds(Collection<Long> ids) {
|
|
627
|
-
// 查询要删除的数据
|
|
628
|
-
List<Order> orders = baseMapper.selectByIds(ids);
|
|
629
|
-
|
|
630
|
-
// 非超管需要校验归属
|
|
631
|
-
if (!LoginHelper.isSuperAdmin()) {
|
|
632
|
-
Long currentUserId = LoginHelper.getUserId();
|
|
633
|
-
boolean hasUnauthorized = orders.stream()
|
|
634
|
-
.anyMatch(o -> !o.getUserId().equals(currentUserId));
|
|
635
|
-
if (hasUnauthorized) {
|
|
636
|
-
throw new ServiceException("包含无权删除的数据");
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
return baseMapper.deleteByIds(ids);
|
|
641
|
-
}
|
|
311
|
+
// 批量操作同样校验归属
|
|
642
312
|
```
|
|
643
313
|
|
|
644
|
-
###
|
|
314
|
+
### 敏感信息泄露
|
|
645
315
|
|
|
646
316
|
```java
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
//
|
|
650
|
-
private static final Set<String> ALLOWED_TYPES = Set.of(
|
|
651
|
-
"image/jpeg", "image/png", "image/gif", "image/webp"
|
|
652
|
-
);
|
|
653
|
-
|
|
654
|
-
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
655
|
-
|
|
656
|
-
public void uploadFile(MultipartFile file) {
|
|
657
|
-
// 1. 校验文件类型
|
|
658
|
-
String contentType = file.getContentType();
|
|
659
|
-
if (!ALLOWED_TYPES.contains(contentType)) {
|
|
660
|
-
throw new ServiceException("不支持的文件类型");
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// 2. 校验文件大小
|
|
664
|
-
if (file.getSize() > MAX_FILE_SIZE) {
|
|
665
|
-
throw new ServiceException("文件大小不能超过10MB");
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// 3. 校验文件扩展名
|
|
669
|
-
String originalName = file.getOriginalFilename();
|
|
670
|
-
String extension = getExtension(originalName);
|
|
671
|
-
if (!Set.of(".jpg", ".jpeg", ".png", ".gif", ".webp").contains(extension.toLowerCase())) {
|
|
672
|
-
throw new ServiceException("不支持的文件扩展名");
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// 4. 重命名文件,避免路径穿越
|
|
676
|
-
String newName = UUID.randomUUID() + extension;
|
|
677
|
-
|
|
678
|
-
// 5. 保存文件...
|
|
679
|
-
}
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
### 8.5 敏感信息泄露防护
|
|
683
|
-
|
|
684
|
-
```java
|
|
685
|
-
// ❌ 危险:返回敏感信息
|
|
686
|
-
@GetMapping("/user/{id}")
|
|
687
|
-
public User getUser(@PathVariable Long id) {
|
|
688
|
-
return userDao.getById(id); // 包含密码等敏感字段
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// ✅ 安全:使用 VO 过滤敏感字段
|
|
692
|
-
@GetMapping("/getUser/{id}")
|
|
693
|
-
public R<UserVo> getUser(@PathVariable Long id) {
|
|
694
|
-
User user = userDao.getById(id);
|
|
695
|
-
return R.ok(MapstructUtils.convert(user, UserVo.class)); // VO 不包含密码字段
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// ✅ 使用 @Sensitive 注解自动脱敏
|
|
699
|
-
public class UserVo {
|
|
700
|
-
private Long id;
|
|
701
|
-
private String userName;
|
|
702
|
-
|
|
703
|
-
@Sensitive(strategy = SensitiveStrategy.PHONE)
|
|
704
|
-
private String phone; // 自动脱敏为 138****8888
|
|
705
|
-
}
|
|
317
|
+
// NG: return userDao.getById(id); // 包含密码等
|
|
318
|
+
// OK: return MapstructUtils.convert(user, UserVo.class); // VO 过滤敏感字段
|
|
319
|
+
// OK: 使用 @Sensitive 自动脱敏
|
|
706
320
|
```
|
|
707
321
|
|
|
708
322
|
---
|
|
@@ -711,38 +325,29 @@ public class UserVo {
|
|
|
711
325
|
|
|
712
326
|
### 代码审查
|
|
713
327
|
|
|
714
|
-
- [ ]
|
|
715
|
-
- [ ] SQL
|
|
716
|
-
- [ ] 敏感字段使用 `@Sensitive`
|
|
717
|
-
- [ ]
|
|
718
|
-
- [ ] Controller
|
|
719
|
-
- [ ]
|
|
720
|
-
- [ ]
|
|
721
|
-
- [ ]
|
|
722
|
-
- [ ]
|
|
723
|
-
- [ ]
|
|
724
|
-
|
|
725
|
-
###
|
|
726
|
-
|
|
727
|
-
- [ ]
|
|
728
|
-
- [ ]
|
|
729
|
-
- [ ] Token
|
|
730
|
-
- [ ] CORS
|
|
731
|
-
- [ ]
|
|
732
|
-
|
|
733
|
-
### 部署检查
|
|
734
|
-
|
|
735
|
-
- [ ] 启用 HTTPS?
|
|
736
|
-
- [ ] 配置安全响应头(X-Frame-Options、X-XSS-Protection 等)?
|
|
737
|
-
- [ ] 错误页面不泄露堆栈信息?
|
|
738
|
-
- [ ] 数据库端口不对外暴露?
|
|
739
|
-
- [ ] Redis 设置了密码?
|
|
328
|
+
- [ ] 用户输入经过 `@NotBlank`/`@Size`/`@Pattern` 校验
|
|
329
|
+
- [ ] SQL 使用 MyBatis-Plus 或参数化查询(#{})
|
|
330
|
+
- [ ] 敏感字段使用 `@Sensitive` 脱敏
|
|
331
|
+
- [ ] 需加密字段使用 `@EncryptField`
|
|
332
|
+
- [ ] Controller 添加 `@SaCheckPermission`
|
|
333
|
+
- [ ] 敏感操作添加 `@RepeatSubmit`
|
|
334
|
+
- [ ] 高频接口添加 `@RateLimiter`
|
|
335
|
+
- [ ] 批量操作校验数据归属(防越权)
|
|
336
|
+
- [ ] 文件上传校验类型/大小/扩展名
|
|
337
|
+
- [ ] 日志中无敏感信息(或已脱敏)
|
|
338
|
+
|
|
339
|
+
### 配置 & 部署
|
|
340
|
+
|
|
341
|
+
- [ ] 生产关闭调试模式
|
|
342
|
+
- [ ] 敏感配置已加密或使用环境变量
|
|
343
|
+
- [ ] Token 有效期合理(2-24h)
|
|
344
|
+
- [ ] CORS 不使用 `*`
|
|
345
|
+
- [ ] 启用 HTTPS、安全响应头
|
|
346
|
+
- [ ] 错误页不泄露堆栈、数据库/Redis 端口不对外
|
|
740
347
|
|
|
741
348
|
---
|
|
742
349
|
|
|
743
|
-
|
|
744
|
-
|
|
745
350
|
## 注意事项
|
|
746
351
|
|
|
747
|
-
-
|
|
748
|
-
- leniu
|
|
352
|
+
- leniu-tengyun-core 项目请使用 `leniu-security-guard` skill
|
|
353
|
+
- leniu 使用自研 secure 模块,注解和工具类与 RuoYi-Vue-Plus 不同
|