ai-engineering-init 1.7.0 → 1.8.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.
Files changed (118) hide show
  1. package/.claude/hooks/skill-forced-eval.js +46 -62
  2. package/.claude/settings.json +10 -1
  3. package/.claude/skills/api-development/SKILL.md +179 -130
  4. package/.claude/skills/architecture-design/SKILL.md +102 -212
  5. package/.claude/skills/backend-annotations/SKILL.md +166 -220
  6. package/.claude/skills/bug-detective/SKILL.md +225 -186
  7. package/.claude/skills/code-patterns/SKILL.md +127 -244
  8. package/.claude/skills/collaborating-with-codex/SKILL.md +96 -113
  9. package/.claude/skills/crud-development/SKILL.md +226 -307
  10. package/.claude/skills/data-permission/SKILL.md +131 -202
  11. package/.claude/skills/database-ops/SKILL.md +158 -355
  12. package/.claude/skills/error-handler/SKILL.md +224 -285
  13. package/.claude/skills/file-oss-management/SKILL.md +174 -169
  14. package/.claude/skills/git-workflow/SKILL.md +123 -341
  15. package/.claude/skills/json-serialization/SKILL.md +121 -137
  16. package/.claude/skills/performance-doctor/SKILL.md +83 -89
  17. package/.claude/skills/redis-cache/SKILL.md +134 -185
  18. package/.claude/skills/scheduled-jobs/SKILL.md +187 -224
  19. package/.claude/skills/security-guard/SKILL.md +168 -276
  20. package/.claude/skills/sms-mail/SKILL.md +266 -228
  21. package/.claude/skills/social-login/SKILL.md +257 -195
  22. package/.claude/skills/tenant-management/SKILL.md +172 -188
  23. package/.claude/skills/utils-toolkit/SKILL.md +214 -222
  24. package/.claude/skills/websocket-sse/SKILL.md +251 -172
  25. package/.claude/skills/workflow-engine/SKILL.md +178 -250
  26. package/.codex/skills/api-development/SKILL.md +179 -130
  27. package/.codex/skills/architecture-design/SKILL.md +102 -212
  28. package/.codex/skills/backend-annotations/SKILL.md +166 -220
  29. package/.codex/skills/bug-detective/SKILL.md +225 -186
  30. package/.codex/skills/code-patterns/SKILL.md +127 -244
  31. package/.codex/skills/collaborating-with-codex/SKILL.md +96 -113
  32. package/.codex/skills/crud-development/SKILL.md +226 -307
  33. package/.codex/skills/data-permission/SKILL.md +131 -202
  34. package/.codex/skills/database-ops/SKILL.md +158 -355
  35. package/.codex/skills/error-handler/SKILL.md +224 -285
  36. package/.codex/skills/file-oss-management/SKILL.md +174 -169
  37. package/.codex/skills/git-workflow/SKILL.md +123 -341
  38. package/.codex/skills/json-serialization/SKILL.md +121 -137
  39. package/.codex/skills/performance-doctor/SKILL.md +83 -89
  40. package/.codex/skills/redis-cache/SKILL.md +134 -185
  41. package/.codex/skills/scheduled-jobs/SKILL.md +187 -224
  42. package/.codex/skills/security-guard/SKILL.md +168 -276
  43. package/.codex/skills/sms-mail/SKILL.md +266 -228
  44. package/.codex/skills/social-login/SKILL.md +257 -195
  45. package/.codex/skills/tenant-management/SKILL.md +172 -188
  46. package/.codex/skills/utils-toolkit/SKILL.md +214 -222
  47. package/.codex/skills/websocket-sse/SKILL.md +251 -172
  48. package/.codex/skills/workflow-engine/SKILL.md +178 -250
  49. package/.cursor/hooks/cursor-skill-eval.js +66 -6
  50. package/.cursor/skills/api-development/SKILL.md +179 -130
  51. package/.cursor/skills/architecture-design/SKILL.md +102 -212
  52. package/.cursor/skills/backend-annotations/SKILL.md +166 -220
  53. package/.cursor/skills/bug-detective/SKILL.md +225 -186
  54. package/.cursor/skills/code-patterns/SKILL.md +127 -244
  55. package/.cursor/skills/collaborating-with-codex/SKILL.md +96 -113
  56. package/.cursor/skills/crud-development/SKILL.md +226 -307
  57. package/.cursor/skills/data-permission/SKILL.md +131 -202
  58. package/.cursor/skills/database-ops/SKILL.md +158 -355
  59. package/.cursor/skills/error-handler/SKILL.md +224 -285
  60. package/.cursor/skills/file-oss-management/SKILL.md +174 -169
  61. package/.cursor/skills/git-workflow/SKILL.md +123 -341
  62. package/.cursor/skills/json-serialization/SKILL.md +121 -137
  63. package/.cursor/skills/performance-doctor/SKILL.md +83 -89
  64. package/.cursor/skills/redis-cache/SKILL.md +134 -185
  65. package/.cursor/skills/scheduled-jobs/SKILL.md +187 -224
  66. package/.cursor/skills/security-guard/SKILL.md +168 -276
  67. package/.cursor/skills/sms-mail/SKILL.md +266 -228
  68. package/.cursor/skills/social-login/SKILL.md +257 -195
  69. package/.cursor/skills/tenant-management/SKILL.md +172 -188
  70. package/.cursor/skills/utils-toolkit/SKILL.md +214 -222
  71. package/.cursor/skills/websocket-sse/SKILL.md +251 -172
  72. package/.cursor/skills/workflow-engine/SKILL.md +178 -250
  73. package/AGENTS.md +49 -540
  74. package/CLAUDE.md +73 -119
  75. package/README.md +37 -6
  76. package/bin/index.js +5 -1
  77. package/package.json +1 -1
  78. package/src/skills/api-development/SKILL.md +179 -130
  79. package/src/skills/architecture-design/SKILL.md +102 -212
  80. package/src/skills/backend-annotations/SKILL.md +166 -220
  81. package/src/skills/bug-detective/SKILL.md +225 -186
  82. package/src/skills/code-patterns/SKILL.md +127 -244
  83. package/src/skills/collaborating-with-codex/SKILL.md +96 -113
  84. package/src/skills/crud-development/SKILL.md +226 -307
  85. package/src/skills/data-permission/SKILL.md +131 -202
  86. package/src/skills/database-ops/SKILL.md +158 -355
  87. package/src/skills/error-handler/SKILL.md +224 -285
  88. package/src/skills/file-oss-management/SKILL.md +174 -169
  89. package/src/skills/git-workflow/SKILL.md +123 -341
  90. package/src/skills/json-serialization/SKILL.md +121 -137
  91. package/src/skills/performance-doctor/SKILL.md +83 -89
  92. package/src/skills/redis-cache/SKILL.md +134 -185
  93. package/src/skills/scheduled-jobs/SKILL.md +187 -224
  94. package/src/skills/security-guard/SKILL.md +168 -276
  95. package/src/skills/sms-mail/SKILL.md +266 -228
  96. package/src/skills/social-login/SKILL.md +257 -195
  97. package/src/skills/tenant-management/SKILL.md +172 -188
  98. package/src/skills/utils-toolkit/SKILL.md +214 -222
  99. package/src/skills/websocket-sse/SKILL.md +251 -172
  100. package/src/skills/workflow-engine/SKILL.md +178 -250
  101. package/.claude/skills/skill-creator/LICENSE.txt +0 -202
  102. package/.claude/skills/skill-creator/SKILL.md +0 -479
  103. package/.claude/skills/skill-creator/agents/analyzer.md +0 -274
  104. package/.claude/skills/skill-creator/agents/comparator.md +0 -202
  105. package/.claude/skills/skill-creator/agents/grader.md +0 -223
  106. package/.claude/skills/skill-creator/assets/eval_review.html +0 -146
  107. package/.claude/skills/skill-creator/eval-viewer/generate_review.py +0 -471
  108. package/.claude/skills/skill-creator/eval-viewer/viewer.html +0 -1325
  109. package/.claude/skills/skill-creator/references/schemas.md +0 -430
  110. package/.claude/skills/skill-creator/scripts/__init__.py +0 -0
  111. package/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
  112. package/.claude/skills/skill-creator/scripts/generate_report.py +0 -326
  113. package/.claude/skills/skill-creator/scripts/improve_description.py +0 -248
  114. package/.claude/skills/skill-creator/scripts/package_skill.py +0 -136
  115. package/.claude/skills/skill-creator/scripts/quick_validate.py +0 -103
  116. package/.claude/skills/skill-creator/scripts/run_eval.py +0 -310
  117. package/.claude/skills/skill-creator/scripts/run_loop.py +0 -332
  118. package/.claude/skills/skill-creator/scripts/utils.py +0 -47
@@ -1,266 +1,328 @@
1
1
  ---
2
2
  name: social-login
3
3
  description: |
4
- 当需要实现第三方登录、OAuth2 认证、社交账号绑定时自动使用此 Skill。
5
-
4
+ 通用 OAuth2 第三方登录开发指南。涵盖授权码流程、接口设计、账号绑定机制、多平台接入。
6
5
  触发场景:
7
- - 需要接入微信/QQ/支付宝等第三方登录
8
- - 需要实现 OAuth2 授权流程
9
- - 需要配置 JustAuth 第三方登录
10
- - 需要实现社交账号与系统账号绑定
11
- - 需要获取第三方用户信息
12
-
13
- 触发词:第三方登录、微信登录、QQ登录、OAuth、OAuth2、JustAuth、社交登录、扫码登录、AuthRequest、SocialUtils、授权登录、GitHub登录、钉钉登录
6
+ - 接入微信/QQ/GitHub 等第三方登录
7
+ - 实现 OAuth2 授权码流程
8
+ - 实现社交账号与系统账号绑定/解绑
9
+ - 获取第三方用户信息
10
+ 触发词:第三方登录、OAuth、OAuth2、社交登录、微信登录、QQ登录、GitHub登录、扫码登录、授权码、授权登录
11
+ 注意:如果项目有专属技能,优先使用专属版本。
14
12
  ---
15
13
 
16
- # 第三方登录开发指南(JustAuth)
14
+ # OAuth2 第三方登录开发指南
17
15
 
18
- > **适用模块**:`ruoyi-common-social`(基于 JustAuth)
19
- > **特性**:Sa-Token 认证集成、Redis 状态缓存(防 CSRF)、多租户支持、账号绑定机制
16
+ > 通用模板。如果项目有专属技能,优先使用。
20
17
 
21
- ## 一、支持平台
18
+ ## 设计原则
22
19
 
23
- | 平台 | source 标识 | 平台 | source 标识 |
24
- |------|------------|------|------------|
25
- | 钉钉 | `dingtalk` | GitHub | `github` |
26
- | Gitee | `gitee` | 微博 | `weibo` |
27
- | 支付宝 | `alipay_wallet` | QQ | `qq` |
28
- | 微信开放平台 | `wechat_open` | 微信公众号 | `wechat_mp` |
29
- | 企业微信 | `wechat_enterprise` | 抖音 | `douyin` |
30
- | 华为 | `huawei` | 微软 | `microsoft` |
31
- | MaxKey | `maxkey` | TopIAM | `topiam` |
32
- | GitLab | `gitlab` | Gitea | `gitea` |
33
-
34
- 完整平台列表及特殊配置详见 `references/provider-configs.md`。
20
+ 1. **标准协议**:遵循 OAuth 2.0 授权码模式(Authorization Code Flow),这是最安全的 OAuth 流程。
21
+ 2. **状态防护**:使用 `state` 参数防止 CSRF 攻击,每次授权请求生成唯一 state 并校验。
22
+ 3. **绑定机制**:第三方账号与系统账号通过绑定表关联,支持一个系统账号绑定多个第三方平台。
23
+ 4. **信息最小化**:只获取必要的第三方用户信息(OpenID、昵称、头像),不过度获取。
35
24
 
36
25
  ---
37
26
 
38
- ## 二、基础配置
27
+ ## OAuth 2.0 授权码流程
39
28
 
40
- ```yaml
41
- justauth:
42
- address: https://your-domain.com # 回调地址前缀
43
- type:
44
- github:
45
- client-id: ${GITHUB_CLIENT_ID:}
46
- client-secret: ${GITHUB_CLIENT_SECRET:}
47
- redirect-uri: ${justauth.address}/social-callback?source=github
48
- gitee:
49
- client-id: ${GITEE_CLIENT_ID:}
50
- client-secret: ${GITEE_CLIENT_SECRET:}
51
- redirect-uri: ${justauth.address}/social-callback?source=gitee
52
- dingtalk:
53
- client-id: ${DINGTALK_APP_KEY:}
54
- client-secret: ${DINGTALK_APP_SECRET:}
55
- redirect-uri: ${justauth.address}/social-callback?source=dingtalk
29
+ ```
30
+ 用户 -> 前端 -> 后端(生成授权URL) -> 第三方平台(授权页)
31
+ |
32
+ 用户授权 |
33
+ v
34
+ 第三方平台 -> 前端回调页(携带 code + state)-> 后端
35
+ |
36
+ 后端用 code 换 access_token |
37
+ 后端用 access_token 获取用户信息 |
38
+ v
39
+ 查绑定关系 -> 登录/绑定
56
40
  ```
57
41
 
58
- > 各平台特殊配置(微软 tenantId、企业微信 agentId、支付宝公钥等)详见 `references/provider-configs.md`。
42
+ ### 步骤详解
59
43
 
60
- ---
44
+ | 步骤 | 描述 | 关键参数 |
45
+ |------|------|---------|
46
+ | 1. 构建授权 URL | 拼接第三方授权地址 | client_id, redirect_uri, state, scope |
47
+ | 2. 用户授权 | 用户在第三方平台确认授权 | - |
48
+ | 3. 回调获取 code | 第三方重定向回应用 | code, state |
49
+ | 4. code 换 token | 后端调用第三方 Token 接口 | code, client_id, client_secret |
50
+ | 5. 获取用户信息 | 后端调用第三方用户信息接口 | access_token |
51
+ | 6. 登录/绑定 | 根据 OpenID 查找绑定关系 | openId, source |
61
52
 
62
- ## 三、核心 API
53
+ ---
63
54
 
64
- ### 3.1 SocialUtils
55
+ ## 实现模式
65
56
 
66
- **位置**:`org.dromara.common.social.utils.SocialUtils`
57
+ ### 一、抽象接口设计
67
58
 
68
59
  ```java
69
- import org.dromara.common.social.utils.SocialUtils;
70
- import me.zhyd.oauth.model.AuthResponse;
71
- import me.zhyd.oauth.model.AuthUser;
72
- import me.zhyd.oauth.request.AuthRequest;
73
-
74
- // 获取授权请求对象
75
- AuthRequest authRequest = SocialUtils.getAuthRequest("github", socialProperties);
76
-
77
- // 生成授权 URL
78
- String authorizeUrl = authRequest.authorize(state);
60
+ // 第三方认证请求接口
61
+ public interface SocialAuthProvider {
62
+ String getSource(); // 平台标识
63
+ String buildAuthorizeUrl(String state); // 构建授权URL
64
+ SocialUser authenticate(String code, String state); // 回调认证
65
+ }
79
66
 
80
- // 处理回调登录
81
- AuthResponse<AuthUser> response = SocialUtils.loginAuth(
82
- "github", code, state, socialProperties
83
- );
84
- if (response.ok()) {
85
- AuthUser user = response.getData();
86
- String openId = user.getUuid(); // 唯一标识
87
- String nickname = user.getNickname(); // 昵称
88
- String source = user.getSource(); // 来源平台
67
+ // 第三方用户信息
68
+ @Data
69
+ public class SocialUser {
70
+ private String openId; // 平台唯一标识
71
+ private String source; // 来源平台(github, wechat_open 等)
72
+ private String nickname; // 昵称
73
+ private String avatar; // 头像
74
+ private String email; // 邮箱(可能为空)
75
+ private String accessToken; // 第三方 Token
76
+ private Map<String, Object> rawInfo; // 原始数据
89
77
  }
90
78
  ```
91
79
 
92
- ### 3.2 AuthUser 关键字段
80
+ ### 二、GitHub 实现示例
93
81
 
94
- | 属性 | 说明 | 属性 | 说明 |
95
- |------|------|------|------|
96
- | `uuid` | 平台用户唯一ID | `username` | 用户名 |
97
- | `nickname` | 昵称 | `avatar` | 头像 |
98
- | `email` | 邮箱 | `source` | 来源平台 |
99
- | `token` | Token 信息 | `rawUserInfo` | 原始数据(Map) |
82
+ ```java
83
+ @Component
84
+ public class GitHubAuthProvider implements SocialAuthProvider {
100
85
 
101
- ### 3.3 状态缓存
86
+ @Value("${social.github.client-id}")
87
+ private String clientId;
102
88
 
103
- `AuthRedisStateCache` 自动管理 OAuth2 state 参数(Redis 缓存,3分钟过期),无需手动操作。
89
+ @Value("${social.github.client-secret}")
90
+ private String clientSecret;
104
91
 
105
- ---
92
+ @Value("${social.github.redirect-uri}")
93
+ private String redirectUri;
106
94
 
107
- ## 四、后端实现
95
+ @Override
96
+ public String getSource() { return "github"; }
108
97
 
109
- ### 4.1 生成授权 URL
98
+ @Override
99
+ public String buildAuthorizeUrl(String state) {
100
+ return "https://github.com/login/oauth/authorize"
101
+ + "?client_id=" + clientId
102
+ + "&redirect_uri=" + URLEncoder.encode(redirectUri, StandardCharsets.UTF_8)
103
+ + "&state=" + state
104
+ + "&scope=user:email";
105
+ }
110
106
 
111
- ```java
112
- @GetMapping("/binding/{source}")
113
- public R<String> authBinding(@PathVariable String source) {
114
- AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
115
- String state = AuthStateUtils.createState();
116
- return R.ok("操作成功", authRequest.authorize(state));
107
+ @Override
108
+ public SocialUser authenticate(String code, String state) {
109
+ // 1. code access_token
110
+ String tokenUrl = "https://github.com/login/oauth/access_token";
111
+ Map<String, String> body = Map.of(
112
+ "client_id", clientId,
113
+ "client_secret", clientSecret,
114
+ "code", code
115
+ );
116
+ String accessToken = httpPost(tokenUrl, body); // 解析响应获取 token
117
+
118
+ // 2. 获取用户信息
119
+ String userInfo = httpGet("https://api.github.com/user",
120
+ Map.of("Authorization", "Bearer " + accessToken));
121
+
122
+ // 3. 构建 SocialUser
123
+ SocialUser user = new SocialUser();
124
+ user.setOpenId(parseField(userInfo, "id"));
125
+ user.setSource("github");
126
+ user.setNickname(parseField(userInfo, "login"));
127
+ user.setAvatar(parseField(userInfo, "avatar_url"));
128
+ user.setEmail(parseField(userInfo, "email"));
129
+ return user;
130
+ }
117
131
  }
118
132
  ```
119
133
 
120
- ### 4.2 回调登录(SocialAuthStrategy)
134
+ ### 三、Controller
121
135
 
122
136
  ```java
123
- @Slf4j
124
- @Service("social" + IAuthStrategy.BASE_NAME)
125
- @RequiredArgsConstructor
126
- public class SocialAuthStrategy implements IAuthStrategy {
137
+ @RestController
138
+ @RequestMapping("/auth/social")
139
+ public class SocialLoginController {
140
+
141
+ @Autowired
142
+ private Map<String, SocialAuthProvider> providers; // Spring 自动注入所有实现
143
+
144
+ @Autowired
145
+ private StringRedisTemplate redisTemplate;
146
+
147
+ @Autowired
148
+ private SocialBindService bindService;
149
+
150
+ // 1. 获取授权 URL
151
+ @GetMapping("/authorize/{source}")
152
+ public Result<String> authorize(@PathVariable String source) {
153
+ SocialAuthProvider provider = getProvider(source);
154
+ String state = UUID.randomUUID().toString().replace("-", "");
155
+ // state 存入 Redis,3分钟有效
156
+ redisTemplate.opsForValue().set("social:state:" + state, source, 3, TimeUnit.MINUTES);
157
+ return Result.ok(provider.buildAuthorizeUrl(state));
158
+ }
127
159
 
128
- @Override
129
- public LoginVo login(String body, SysClientVo client) {
130
- SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
131
- ValidatorUtils.validate(loginBody);
132
-
133
- // 1. 获取第三方用户信息
134
- AuthResponse<AuthUser> response = SocialUtils.loginAuth(
135
- loginBody.getSource(), loginBody.getSocialCode(),
136
- loginBody.getSocialState(), socialProperties);
137
- if (!response.ok()) {
138
- throw new ServiceException(response.getMsg());
160
+ // 2. 回调登录
161
+ @PostMapping("/callback")
162
+ public Result<?> callback(@RequestBody SocialCallbackDTO dto) {
163
+ // 校验 state
164
+ String cachedSource = redisTemplate.opsForValue().get("social:state:" + dto.getState());
165
+ if (cachedSource == null) {
166
+ throw new [你的异常类]("授权已过期,请重新操作");
139
167
  }
168
+ redisTemplate.delete("social:state:" + dto.getState());
169
+
170
+ // 获取第三方用户信息
171
+ SocialAuthProvider provider = getProvider(dto.getSource());
172
+ SocialUser socialUser = provider.authenticate(dto.getCode(), dto.getState());
140
173
 
141
- // 2. 查找绑定关系
142
- String authId = response.getData().getSource() + response.getData().getUuid();
143
- List<SysSocialVo> list = sysSocialService.selectByAuthId(authId);
144
- if (CollUtil.isEmpty(list)) {
145
- throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
174
+ // 查找绑定关系
175
+ String authId = socialUser.getSource() + ":" + socialUser.getOpenId();
176
+ SocialBind bind = bindService.findByAuthId(authId);
177
+
178
+ if (bind == null) {
179
+ // 未绑定 -> 返回第三方信息,引导绑定或注册
180
+ return Result.fail("NEED_BINDIND", "请绑定系统账号", socialUser);
146
181
  }
147
182
 
148
- // 3. 生成系统 Token
149
- LoginUser loginUser = loginService.buildLoginUser(loadUser(list.get(0).getUserId()));
150
- LoginHelper.login(loginUser, new SaLoginParameter()
151
- .setDeviceType(client.getDeviceType())
152
- .setTimeout(client.getTimeout())
153
- .setActiveTimeout(client.getActiveTimeout()));
154
-
155
- LoginVo loginVo = new LoginVo();
156
- loginVo.setAccessToken(StpUtil.getTokenValue());
157
- loginVo.setExpireIn(StpUtil.getTokenTimeout());
158
- return loginVo;
183
+ // 已绑定 -> 执行登录
184
+ LoginUser loginUser = loadUserById(bind.getUserId());
185
+ String token = [你的认证工具类].login(loginUser);
186
+ return Result.ok(Map.of("token", token));
159
187
  }
160
- }
161
- ```
162
188
 
163
- ### 4.3 账号绑定
189
+ // 3. 绑定(已登录用户绑定第三方账号)
190
+ @PostMapping("/bind")
191
+ public Result<?> bind(@RequestBody SocialCallbackDTO dto) {
192
+ [你的认证工具类].checkLogin();
193
+ Long currentUserId = [你的认证工具类].getCurrentUserId();
164
194
 
165
- ```java
166
- // 绑定:AuthController.socialCallback() → SysLoginService.socialRegister()
167
- @PostMapping("/social/callback")
168
- public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
169
- StpUtil.checkLogin();
170
- AuthResponse<AuthUser> response = SocialUtils.loginAuth(...);
171
- if (!response.ok()) return R.fail(response.getMsg());
172
- loginService.socialRegister(response.getData());
173
- return R.ok();
174
- }
195
+ SocialAuthProvider provider = getProvider(dto.getSource());
196
+ SocialUser socialUser = provider.authenticate(dto.getCode(), dto.getState());
175
197
 
176
- // socialRegister 核心逻辑:
177
- // 1. 生成 authId = source + uuid
178
- // 2. 检查 authId 是否已被其他用户绑定
179
- // 3. 查询当前用户是否已绑定该平台 -> 新增或更新
180
- ```
198
+ String authId = socialUser.getSource() + ":" + socialUser.getOpenId();
199
+ bindService.bindOrUpdate(currentUserId, authId, socialUser);
200
+ return Result.ok("绑定成功");
201
+ }
181
202
 
182
- ### 4.4 解绑
203
+ // 4. 解绑
204
+ @DeleteMapping("/unbind/{bindId}")
205
+ public Result<?> unbind(@PathVariable Long bindId) {
206
+ [你的认证工具类].checkLogin();
207
+ bindService.unbind(bindId, [你的认证工具类].getCurrentUserId());
208
+ return Result.ok("已解除绑定");
209
+ }
183
210
 
184
- ```java
185
- @DeleteMapping("/unlock/{socialId}")
186
- public R<Void> unlockSocial(@PathVariable Long socialId) {
187
- StpUtil.checkLogin();
188
- return socialUserService.deleteWithValidById(socialId) ? R.ok() : R.fail("取消授权失败");
211
+ private SocialAuthProvider getProvider(String source) {
212
+ // providers Map 的 key 是 Bean 名称,需要匹配 source
213
+ return providers.values().stream()
214
+ .filter(p -> p.getSource().equals(source))
215
+ .findFirst()
216
+ .orElseThrow(() -> new [你的异常类]("不支持的登录平台: " + source));
217
+ }
189
218
  }
190
219
  ```
191
220
 
192
- ---
193
-
194
- ## 五、前端集成
221
+ ### 四、绑定表设计
222
+
223
+ ```sql
224
+ CREATE TABLE sys_social_bind (
225
+ id BIGINT NOT NULL COMMENT '主键',
226
+ user_id BIGINT NOT NULL COMMENT '系统用户ID',
227
+ auth_id VARCHAR(128) NOT NULL COMMENT '唯一标识 (source:openId)',
228
+ source VARCHAR(32) NOT NULL COMMENT '来源平台',
229
+ open_id VARCHAR(128) NOT NULL COMMENT '平台用户ID',
230
+ nickname VARCHAR(64) COMMENT '昵称',
231
+ avatar VARCHAR(512) COMMENT '头像',
232
+ email VARCHAR(128) COMMENT '邮箱',
233
+ created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
234
+ PRIMARY KEY (id),
235
+ UNIQUE KEY uk_auth_id (auth_id),
236
+ KEY idx_user_id (user_id)
237
+ );
238
+ ```
195
239
 
196
- ```javascript
197
- // 跳转授权
198
- const { data } = await request.get(`/auth/binding/${source}`);
199
- window.location.href = data;
240
+ ### 五、配置
200
241
 
201
- // 回调页面处理
202
- const { source, code, state } = this.$route.query;
203
- const { data } = await request.post('/auth/login', {
204
- grantType: 'social',
205
- source, socialCode: code, socialState: state,
206
- clientId: 'your-client-id'
207
- });
208
- setToken(data.accessToken);
242
+ ```yaml
243
+ social:
244
+ github:
245
+ client-id: ${GITHUB_CLIENT_ID:}
246
+ client-secret: ${GITHUB_CLIENT_SECRET:}
247
+ redirect-uri: https://your-domain.com/social-callback?source=github
248
+ wechat:
249
+ app-id: ${WECHAT_APP_ID:}
250
+ app-secret: ${WECHAT_APP_SECRET:}
251
+ redirect-uri: https://your-domain.com/social-callback?source=wechat_open
209
252
  ```
210
253
 
211
254
  ---
212
255
 
213
- ## 六、常见错误
256
+ ## 选型建议
214
257
 
215
- ```yaml
216
- # ❌ 回调地址与第三方平台配置不一致
217
- redirect-uri: http://localhost:8080/callback
258
+ | 方案 | 优点 | 缺点 | 适用场景 |
259
+ |------|------|------|---------|
260
+ | 自研(如上) | 完全可控、无依赖 | 每个平台需手动对接 | 接入 1-3 个平台 |
261
+ | JustAuth | 开箱即用、20+ 平台 | 引入第三方依赖 | 多平台快速接入 |
262
+ | Spring Security OAuth2 Client | Spring 生态原生 | 配置复杂 | 企业级、标准 OAuth2 |
218
263
 
219
- # ✅ 使用与第三方平台一致的地址
220
- redirect-uri: https://your-domain.com/social-callback?source=github
221
- ```
264
+ ### 常见平台接入
265
+
266
+ | 平台 | 标识 | 特殊要求 |
267
+ |------|------|---------|
268
+ | GitHub | `github` | 无 |
269
+ | 微信开放平台 | `wechat_open` | 需企业开发者认证 |
270
+ | 微信公众号 | `wechat_mp` | 需服务号 |
271
+ | QQ | `qq` | 需备案域名 |
272
+ | 钉钉 | `dingtalk` | 需创建 H5 微应用 |
273
+ | 企业微信 | `wechat_enterprise` | 需 agentId |
274
+ | 支付宝 | `alipay` | 需应用公钥/私钥 |
275
+
276
+ ---
277
+
278
+ ## 常见错误
222
279
 
223
280
  ```java
224
- // 不检查响应结果
225
- AuthUser user = response.getData(); // 可能 null
281
+ // 1. 不校验 state 参数(CSRF 攻击风险)
282
+ SocialUser user = provider.authenticate(code, state);
283
+ // 应先从 Redis 校验 state 是否有效
226
284
 
227
- // 先检查状态
228
- if (!response.ok()) throw new ServiceException(response.getMsg());
285
+ // 2. 回调地址与第三方平台配置不一致
286
+ // 应确保 redirect_uri 与第三方平台配置完全一致(包括协议、域名、路径、参数)
229
287
 
230
- // source 标识拼写错误
231
- SocialUtils.getAuthRequest("wechat", props); // 不存在
288
+ // 3. 不检查认证响应
289
+ SocialUser user = provider.authenticate(code, state);
290
+ user.getOpenId(); // 认证可能失败,user 为 null 或字段缺失
291
+ // 应先检查认证结果
232
292
 
233
- // 正确标识
234
- SocialUtils.getAuthRequest("wechat_open", props); // 微信开放平台
235
- SocialUtils.getAuthRequest("wechat_mp", props); // 微信公众号
236
- ```
293
+ // 4. source 标识拼写错误
294
+ getProvider("wechat"); // 不存在
295
+ getProvider("wechat_open"); // 正确
237
296
 
238
- ---
297
+ // 5. 绑定关系不检查冲突
298
+ // 同一个第三方账号被多个系统账号绑定
299
+ // authId 应设为唯一索引
239
300
 
240
- ## 七、扩展自定义平台
301
+ // 6. 未处理 Token 过期
302
+ // 第三方 access_token 有有效期,需要用 refresh_token 刷新
303
+ // 或每次登录重新获取
241
304
 
242
- ```java
243
- public class AuthCustomRequest extends AuthDefaultRequest {
244
- public AuthCustomRequest(AuthConfig config, AuthStateCache stateCache) {
245
- super(config, AuthCustomSource.CUSTOM, stateCache);
246
- }
247
- @Override
248
- protected AuthToken getAccessToken(AuthCallback authCallback) { ... }
249
- @Override
250
- protected AuthUser getUserInfo(AuthToken authToken) { ... }
251
- }
252
- // 然后在 SocialUtils.getAuthRequest() 中添加对应 case
305
+ // 7. 前端直接传 client_secret
306
+ // client_secret 只能在后端使用,绝不能暴露给前端
253
307
  ```
254
308
 
255
- ---
309
+ ### 前端集成参考
256
310
 
257
- ## 八、参考代码位置
311
+ ```javascript
312
+ // 跳转授权
313
+ const { data: authorizeUrl } = await request.get(`/auth/social/authorize/${source}`);
314
+ window.location.href = authorizeUrl;
258
315
 
259
- | 类型 | 位置 |
260
- |------|------|
261
- | SocialUtils | `ruoyi-common/ruoyi-common-social/.../utils/SocialUtils.java` |
262
- | AuthRedisStateCache | `ruoyi-common/ruoyi-common-social/.../utils/AuthRedisStateCache.java` |
263
- | SocialProperties | `ruoyi-common/ruoyi-common-social/.../config/properties/SocialProperties.java` |
264
- | SocialAuthStrategy | `ruoyi-admin/.../web/service/impl/SocialAuthStrategy.java` |
265
- | AuthController | `ruoyi-admin/.../web/controller/AuthController.java` |
266
- | ISysSocialService | `ruoyi-modules/ruoyi-system/.../service/ISysSocialService.java` |
316
+ // 回调页面处理
317
+ const { source, code, state } = getQueryParams();
318
+ const { data } = await request.post('/auth/social/callback', {
319
+ source, code, state
320
+ });
321
+ if (data.token) {
322
+ setToken(data.token);
323
+ router.push('/');
324
+ } else {
325
+ // 引导绑定或注册
326
+ router.push({ path: '/bindAccount', query: { source, code, state } });
327
+ }
328
+ ```