@linyjs/plugin-docs 0.1.2 → 0.1.4

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.
@@ -0,0 +1,428 @@
1
+ # 用户认证
2
+
3
+ 本指南介绍如何在 linyjs 插件中使用认证系统实现用户登录、注册和权限验证。
4
+
5
+ ## 概述
6
+
7
+ linyjs 提供完整的用户认证系统,支持:
8
+
9
+ - 🔐 用户注册和登录
10
+ - 🪙 JWT Token 认证
11
+ - 🛡️ 路由级别的认证保护
12
+ - 👤 用户信息管理
13
+
14
+ ## 配置认证服务
15
+
16
+ ### 在服务端模块中注入依赖
17
+
18
+ ```typescript
19
+ import type { IModule, IServiceDef } from '@linyjs/server-module-interface';
20
+ import { login, register, verifyToken } from '@linyjs/auth';
21
+
22
+ class AuthController {
23
+ // ... 控制器实现
24
+ }
25
+
26
+ const authControllerDef: IServiceDef = {
27
+ serviceTag: 'authController',
28
+ serviceImpl: AuthController,
29
+ dependencies: [],
30
+ meta: { usageType: 'controller' }
31
+ };
32
+
33
+ export const myServerModule: IModule = {
34
+ name: 'myServer',
35
+ moduleDependencies: ['auth'],
36
+ serviceDefs: [authControllerDef]
37
+ };
38
+ ```
39
+
40
+ ## 基本认证操作
41
+
42
+ ### 用户注册
43
+
44
+ ```typescript
45
+ import { register } from '@linyjs/auth';
46
+
47
+ class AuthController {
48
+ getRoutes() {
49
+ return [{
50
+ method: 'POST' as const,
51
+ path: '/api/auth/register',
52
+ middlewares: [],
53
+ responseType: 'json' as const,
54
+ handler: async (req: any) => {
55
+ const { username, email, password } = req.body;
56
+
57
+ try {
58
+ const result = await register(username, email, password);
59
+ return {
60
+ success: true,
61
+ message: 'Registration successful',
62
+ data: result
63
+ };
64
+ } catch (error) {
65
+ return {
66
+ success: false,
67
+ message: error instanceof Error ? error.message : 'Registration failed'
68
+ };
69
+ }
70
+ }
71
+ }];
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### 用户登录
77
+
78
+ ```typescript
79
+ import { login } from '@linyjs/auth';
80
+
81
+ class AuthController {
82
+ getRoutes() {
83
+ return [{
84
+ method: 'POST' as const,
85
+ path: '/api/auth/login',
86
+ middlewares: [],
87
+ responseType: 'json' as const,
88
+ handler: async (req: any) => {
89
+ const { identifier, password } = req.body;
90
+
91
+ try {
92
+ const result = await login(identifier, password);
93
+ return {
94
+ success: true,
95
+ message: 'Login successful',
96
+ data: result
97
+ };
98
+ } catch (error) {
99
+ return {
100
+ success: false,
101
+ message: error instanceof Error ? error.message : 'Login failed'
102
+ };
103
+ }
104
+ }
105
+ }];
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### Token 验证
111
+
112
+ ```typescript
113
+ import { verifyToken, getUserById } from '@linyjs/auth';
114
+
115
+ class AuthController {
116
+ getRoutes() {
117
+ return [{
118
+ method: 'GET' as const,
119
+ path: '/api/auth/me',
120
+ middlewares: [],
121
+ responseType: 'json' as const,
122
+ handler: async (req: any) => {
123
+ const token = req.headers.authorization?.replace('Bearer ', '');
124
+
125
+ if (!token) {
126
+ return {
127
+ success: false,
128
+ message: 'No token provided'
129
+ };
130
+ }
131
+
132
+ try {
133
+ const payload = await verifyToken(token);
134
+ const user = await getUserById(payload.userId);
135
+
136
+ return {
137
+ success: true,
138
+ data: user
139
+ };
140
+ } catch (error) {
141
+ return {
142
+ success: false,
143
+ message: 'Invalid token'
144
+ };
145
+ }
146
+ }
147
+ }];
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## 认证中间件
153
+
154
+ ### 创建认证中间件
155
+
156
+ ```typescript
157
+ import { verifyToken, getUserById } from '@linyjs/auth';
158
+
159
+ async function authMiddleware(req: any, res: any, next: any) {
160
+ const token = req.headers.authorization?.replace('Bearer ', '');
161
+
162
+ if (!token) {
163
+ return res.status(401).json({
164
+ success: false,
165
+ message: 'Authentication token required'
166
+ });
167
+ }
168
+
169
+ try {
170
+ const payload = await verifyToken(token);
171
+ const user = await getUserById(payload.userId);
172
+
173
+ if (!user) {
174
+ return res.status(401).json({
175
+ success: false,
176
+ message: 'User not found'
177
+ });
178
+ }
179
+
180
+ // 将用户信息添加到请求对象
181
+ req.user = user;
182
+ req.userId = user._id;
183
+
184
+ next();
185
+ } catch (error) {
186
+ return res.status(401).json({
187
+ success: false,
188
+ message: 'Invalid authentication token'
189
+ });
190
+ }
191
+ }
192
+ ```
193
+
194
+ ### 使用认证中间件
195
+
196
+ ```typescript
197
+ class ProtectedController {
198
+ getRoutes() {
199
+ return [{
200
+ method: 'GET' as const,
201
+ path: '/api/protected',
202
+ middlewares: [authMiddleware], // 使用认证中间件
203
+ responseType: 'json' as const,
204
+ handler: async (req: any) => {
205
+ return {
206
+ success: true,
207
+ message: `Hello ${req.user.username}! You have access to this protected route.`
208
+ };
209
+ }
210
+ }];
211
+ }
212
+ }
213
+ ```
214
+
215
+ ## 客户端认证
216
+
217
+ ### 登录表单示例
218
+
219
+ ```typescript
220
+ import type { IModule } from '@linyjs/client-module-interface';
221
+ import React, { useState } from 'react';
222
+
223
+ function LoginForm() {
224
+ const [formData, setFormData] = useState({
225
+ identifier: '',
226
+ password: ''
227
+ });
228
+ const [error, setError] = useState('');
229
+
230
+ const handleSubmit = async (e: React.FormEvent) => {
231
+ e.preventDefault();
232
+
233
+ try {
234
+ const response = await fetch('/api/auth/login', {
235
+ method: 'POST',
236
+ headers: {
237
+ 'Content-Type': 'application/json'
238
+ },
239
+ body: JSON.stringify(formData)
240
+ });
241
+
242
+ const result = await response.json();
243
+
244
+ if (result.success) {
245
+ // 保存 token 到 localStorage 或 cookie
246
+ localStorage.setItem('token', result.data.token);
247
+ // 跳转或刷新页面
248
+ window.location.reload();
249
+ } else {
250
+ setError(result.message);
251
+ }
252
+ } catch (err) {
253
+ setError('Network error');
254
+ }
255
+ };
256
+
257
+ return (
258
+ <form onSubmit={handleSubmit}>
259
+ <div>
260
+ <label>用户名或邮箱:</label>
261
+ <input
262
+ type="text"
263
+ value={formData.identifier}
264
+ onChange={(e) => setFormData({ ...formData, identifier: e.target.value })}
265
+ required
266
+ />
267
+ </div>
268
+ <div>
269
+ <label>密码:</label>
270
+ <input
271
+ type="password"
272
+ value={formData.password}
273
+ onChange={(e) => setFormData({ ...formData, password: e.target.value })}
274
+ required
275
+ />
276
+ </div>
277
+ {error && <div className="error">{error}</div>}
278
+ <button type="submit">登录</button>
279
+ </form>
280
+ );
281
+ }
282
+
283
+ class LoginFormService {
284
+ getComponents() {
285
+ return {
286
+ LoginForm: {
287
+ component: LoginForm,
288
+ meta: { usageType: 'normal_component' }
289
+ }
290
+ };
291
+ }
292
+ }
293
+
294
+ const loginFormDef: IServiceDef = {
295
+ serviceTag: 'loginForm',
296
+ serviceImpl: LoginFormService,
297
+ meta: { usageType: 'normal_service' }
298
+ };
299
+
300
+ export const authClientModule: IModule = {
301
+ name: 'authClient',
302
+ serviceDefs: [loginFormDef]
303
+ };
304
+ ```
305
+
306
+ ## 认证状态管理
307
+
308
+ ### 创建认证 Hook
309
+
310
+ ```typescript
311
+ import { useState, useEffect } from 'react';
312
+
313
+ interface User {
314
+ _id: string;
315
+ username: string;
316
+ email: string;
317
+ createdAt: Date;
318
+ roles?: string[];
319
+ }
320
+
321
+ export function useAuth() {
322
+ const [user, setUser] = useState<User | null>(null);
323
+ const [loading, setLoading] = useState(true);
324
+
325
+ useEffect(() => {
326
+ const token = localStorage.getItem('token');
327
+
328
+ if (!token) {
329
+ setLoading(false);
330
+ return;
331
+ }
332
+
333
+ fetch('/api/auth/me', {
334
+ headers: {
335
+ 'Authorization': `Bearer ${token}`
336
+ }
337
+ })
338
+ .then(response => response.json())
339
+ .then(result => {
340
+ if (result.success) {
341
+ setUser(result.data);
342
+ } else {
343
+ localStorage.removeItem('token');
344
+ }
345
+ })
346
+ .finally(() => {
347
+ setLoading(false);
348
+ });
349
+ }, []);
350
+
351
+ const login = async (identifier: string, password: string) => {
352
+ const response = await fetch('/api/auth/login', {
353
+ method: 'POST',
354
+ headers: {
355
+ 'Content-Type': 'application/json'
356
+ },
357
+ body: JSON.stringify({ identifier, password })
358
+ });
359
+
360
+ const result = await response.json();
361
+
362
+ if (result.success) {
363
+ localStorage.setItem('token', result.data.token);
364
+ setUser(result.data.user);
365
+ return true;
366
+ }
367
+
368
+ return false;
369
+ };
370
+
371
+ const logout = () => {
372
+ localStorage.removeItem('token');
373
+ setUser(null);
374
+ };
375
+
376
+ const isAuthenticated = !!user;
377
+
378
+ return {
379
+ user,
380
+ loading,
381
+ isAuthenticated,
382
+ login,
383
+ logout
384
+ };
385
+ }
386
+ ```
387
+
388
+ ## 最佳实践
389
+
390
+ ### 1. Token 存储
391
+ - 使用 `localStorage` 或 `cookie` 存储 token
392
+ - 考虑安全性时优先使用 `httpOnly` cookie
393
+ - 实现 token 自动刷新机制
394
+
395
+ ### 2. 错误处理
396
+ - 捕获并处理认证相关的错误
397
+ - 提供用户友好的错误消息
398
+ - 记录详细的错误日志用于调试
399
+
400
+ ### 3. 安全性
401
+ - 始终在服务端验证 token
402
+ - 不要在客户端存储敏感信息
403
+ - 使用强密码策略
404
+ - 考虑实现两步验证增强安全性
405
+
406
+ ### 4. 用户体验
407
+ - 记住用户的登录状态
408
+ - 自动续期 token 避免频繁登录
409
+ - 在 token 过期时友好提示用户
410
+
411
+ ## 故障排除
412
+
413
+ ### 常见问题
414
+
415
+ 1. **Token 无法验证**
416
+ - 检查 JWT_SECRET 是否正确配置
417
+ - 确认 token 未过期
418
+ - 验证 token 格式是否正确
419
+
420
+ 2. **用户无法登录**
421
+ - 检查用户名/邮箱和密码是否正确
422
+ - 确认用户账户已激活
423
+ - 检查数据库连接是否正常
424
+
425
+ 3. **中间件无法获取用户信息**
426
+ - 确认 token 在请求头中正确传递
427
+ - 检查 verifyToken 函数是否正常工作
428
+ - 验证 getUserById 是否能找到对应用户
@@ -545,6 +545,12 @@ function MyPage() {
545
545
 
546
546
  ## 最佳实践
547
547
 
548
+ ### 0. 遵循 ServiceDef 代码风格规范
549
+
550
+ > 📐 **重要**: 请阅读 [ServiceDef 代码风格规范](../best-practices/service-definition.md),了解两条核心规则:
551
+ > 1. ServiceDef 必须声明为独立变量,禁止内联在 `serviceDefs` 数组中
552
+ > 2. ServiceDef 声明必须与实现类放在同一个文件中
553
+
548
554
  ### 1. 组件拆分
549
555
 
550
556
  ```