@linyjs/plugin-docs 0.0.15 → 0.0.17
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/README.md +4 -0
- package/docs/api-reference/index.md +3 -0
- package/docs/faq.md +4 -1
- package/docs/guides/authentication.md +313 -240
- package/docs/guides/client-hooks.md +626 -0
- package/docs/guides/client-module.md +5 -2
- package/docs/guides/client-routes.md +631 -0
- package/docs/guides/dependency-injection.md +3 -0
- package/docs/guides/permissions.md +124 -53
- package/docs/guides/server-module.md +2 -0
- package/docs/guides/shared-module-interface.md +117 -51
- package/docs/guides/state-management.md +905 -0
- package/docs/guides/ui-slots.md +4 -1
- package/package.json +1 -1
|
@@ -11,213 +11,191 @@ linyjs 提供完整的用户认证系统,支持:
|
|
|
11
11
|
- 🛡️ 路由级别的认证保护
|
|
12
12
|
- 👤 用户信息管理
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## 内置认证服务
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
linyjs 的 `authServer` 模块已内置完整的认证功能,包括:
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
| 功能 | 说明 |
|
|
19
|
+
|------|------|
|
|
20
|
+
| `POST /api/auth/register` | 用户注册接口 |
|
|
21
|
+
| `POST /api/auth/login` | 用户登录接口 |
|
|
22
|
+
| `GET /api/auth/me` | 获取当前用户信息(需认证) |
|
|
23
|
+
| `auth` 中间件 | JWT Token 验证中间件,middlewareId 为 `'auth'` |
|
|
24
|
+
| `userService` | 用户管理服务(`IUserService`),提供用户列表和角色管理 |
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
// ... 控制器实现
|
|
24
|
-
}
|
|
26
|
+
其他模块**无需自行实现**认证逻辑,只需:
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
dependencies: [],
|
|
30
|
-
meta: { usageType: 'controller' }
|
|
31
|
-
};
|
|
28
|
+
1. 在 `moduleDependencies` 中声明 `'authServer'`
|
|
29
|
+
2. 在路由的 `middlewares` 中引用 `'auth'` 中间件
|
|
30
|
+
3. 需要用户管理时,通过 `dependencies` 注入 `IUserService`
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
name: 'myServer',
|
|
35
|
-
moduleDependencies: ['auth'],
|
|
36
|
-
serviceDefs: [authControllerDef]
|
|
37
|
-
};
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## 基本认证操作
|
|
32
|
+
## 服务端使用认证
|
|
41
33
|
|
|
42
|
-
###
|
|
34
|
+
### 使用 auth 中间件保护路由
|
|
43
35
|
|
|
44
36
|
```typescript
|
|
45
|
-
import {
|
|
37
|
+
import type { IControllerService, IControllerServiceDef, IModule } from '@linyjs/server-module-interface';
|
|
46
38
|
|
|
47
|
-
class
|
|
39
|
+
class ProtectedController implements IControllerService {
|
|
48
40
|
getRoutes() {
|
|
49
41
|
return [{
|
|
50
|
-
method: '
|
|
51
|
-
path: '/api/
|
|
52
|
-
middlewares: [],
|
|
42
|
+
method: 'GET' as const,
|
|
43
|
+
path: '/api/protected',
|
|
44
|
+
middlewares: ['auth'], // ← 引用 authServer 模块提供的 auth 中间件
|
|
53
45
|
responseType: 'json' as const,
|
|
54
|
-
handler: async (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
}
|
|
46
|
+
handler: async (params: Record<string, any>, ctx: any) => {
|
|
47
|
+
// ctx.user 由 auth 中间件注入
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
message: `Hello ${ctx.user.username}! You have access to this protected route.`
|
|
51
|
+
};
|
|
70
52
|
}
|
|
71
53
|
}];
|
|
72
54
|
}
|
|
73
55
|
}
|
|
56
|
+
|
|
57
|
+
const protectedControllerDef: IControllerServiceDef = {
|
|
58
|
+
serviceTag: 'protectedController',
|
|
59
|
+
serviceImpl: ProtectedController,
|
|
60
|
+
meta: { usageType: 'controller' }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const myServerModule: IModule = {
|
|
64
|
+
name: 'myServer',
|
|
65
|
+
moduleDependencies: ['authServer'], // ← 声明模块依赖
|
|
66
|
+
serviceDefs: [protectedControllerDef]
|
|
67
|
+
};
|
|
74
68
|
```
|
|
75
69
|
|
|
76
|
-
|
|
70
|
+
> 💡 `auth` 中间件会自动从请求头提取 Bearer Token,验证后会将用户信息注入 `ctx.user`。
|
|
71
|
+
|
|
72
|
+
### 注入 UserService 进行用户管理
|
|
73
|
+
|
|
74
|
+
当需要获取用户列表或更新用户角色时,通过依赖注入获取 `IUserService` 实例:
|
|
77
75
|
|
|
78
76
|
```typescript
|
|
79
|
-
import {
|
|
77
|
+
import type { IControllerService, IControllerServiceDef, IModule } from '@linyjs/server-module-interface';
|
|
78
|
+
import type { IUserService, ISafeUser } from '@linyjs/shared-module-interface';
|
|
79
|
+
|
|
80
|
+
class AdminController implements IControllerService {
|
|
81
|
+
private userService: IUserService;
|
|
82
|
+
|
|
83
|
+
// ✅ 通过构造函数注入获取用户服务
|
|
84
|
+
constructor({ userService }: { userService: IUserService }) {
|
|
85
|
+
this.userService = userService;
|
|
86
|
+
}
|
|
80
87
|
|
|
81
|
-
class AuthController {
|
|
82
88
|
getRoutes() {
|
|
83
|
-
return [
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
method: 'GET' as const,
|
|
92
|
+
path: '/api/admin/users',
|
|
93
|
+
middlewares: ['auth'],
|
|
94
|
+
responseType: 'json' as const,
|
|
95
|
+
handler: async (params: Record<string, any>, ctx: any) => {
|
|
96
|
+
// 获取所有用户列表(不含密码哈希)
|
|
97
|
+
const users: ISafeUser[] = await this.userService.getAllUsers();
|
|
98
|
+
return { success: true, data: users };
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
method: 'PUT' as const,
|
|
103
|
+
path: '/api/admin/users/:id/roles',
|
|
104
|
+
middlewares: ['auth'],
|
|
105
|
+
responseType: 'json' as const,
|
|
106
|
+
handler: async (params: Record<string, any>, ctx: any) => {
|
|
107
|
+
// 更新用户角色
|
|
108
|
+
const updated = await this.userService.updateUserRoles(
|
|
109
|
+
params.id,
|
|
110
|
+
params.roles
|
|
111
|
+
);
|
|
112
|
+
if (!updated) {
|
|
113
|
+
return { status: 404, data: { error: 'User not found' } };
|
|
114
|
+
}
|
|
115
|
+
return { success: true, data: updated };
|
|
103
116
|
}
|
|
104
117
|
}
|
|
105
|
-
|
|
118
|
+
];
|
|
106
119
|
}
|
|
107
120
|
}
|
|
121
|
+
|
|
122
|
+
// ✅ 在 ServiceDef 中声明 dependencies 引用 userService 的 serviceTag
|
|
123
|
+
const adminControllerDef: IControllerServiceDef = {
|
|
124
|
+
serviceTag: 'adminController',
|
|
125
|
+
serviceImpl: AdminController,
|
|
126
|
+
dependencies: ['authServer.userService'], // ← serviceTag
|
|
127
|
+
meta: { usageType: 'controller' }
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const adminServerModule: IModule = {
|
|
131
|
+
name: 'adminServer',
|
|
132
|
+
moduleDependencies: ['authServer'], // ← 模块依赖
|
|
133
|
+
serviceDefs: [adminControllerDef]
|
|
134
|
+
};
|
|
108
135
|
```
|
|
109
136
|
|
|
110
|
-
###
|
|
137
|
+
### IUserService 接口
|
|
138
|
+
|
|
139
|
+
`IUserService` 通过 `@linyjs/shared-module-interface` 提供类型定义:
|
|
111
140
|
|
|
112
141
|
```typescript
|
|
113
|
-
|
|
142
|
+
interface IUserService {
|
|
143
|
+
/** 获取所有用户列表(不含密码哈希) */
|
|
144
|
+
getAllUsers(): Promise<ISafeUser[]>;
|
|
114
145
|
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
}
|
|
146
|
+
/** 更新用户角色 */
|
|
147
|
+
updateUserRoles(userId: string, roles: string[]): Promise<ISafeUser | null>;
|
|
149
148
|
}
|
|
150
149
|
```
|
|
151
150
|
|
|
152
|
-
|
|
151
|
+
> 📖 更多类型定义请阅读 [共享模块接口](./shared-module-interface.md)。
|
|
153
152
|
|
|
154
|
-
|
|
153
|
+
## 客户端认证
|
|
154
|
+
|
|
155
|
+
### 调用认证 API
|
|
156
|
+
|
|
157
|
+
客户端通过 `fetch` 调用 `authServer` 模块提供的认证 API,无需直接导入认证模块:
|
|
158
|
+
|
|
159
|
+
#### 用户注册
|
|
155
160
|
|
|
156
161
|
```typescript
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
}
|
|
162
|
+
async function register(username: string, email: string, password: string) {
|
|
163
|
+
const response = await fetch('/api/auth/register', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: { 'Content-Type': 'application/json' },
|
|
166
|
+
body: JSON.stringify({ username, email, password })
|
|
167
|
+
});
|
|
168
|
+
return response.json();
|
|
191
169
|
}
|
|
192
170
|
```
|
|
193
171
|
|
|
194
|
-
|
|
172
|
+
#### 用户登录
|
|
195
173
|
|
|
196
174
|
```typescript
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
}
|
|
175
|
+
async function login(identifier: string, password: string) {
|
|
176
|
+
const response = await fetch('/api/auth/login', {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: { 'Content-Type': 'application/json' },
|
|
179
|
+
body: JSON.stringify({ identifier, password })
|
|
180
|
+
});
|
|
181
|
+
return response.json();
|
|
212
182
|
}
|
|
213
183
|
```
|
|
214
184
|
|
|
215
|
-
|
|
185
|
+
#### 获取当前用户
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
async function getCurrentUser(token: string) {
|
|
189
|
+
const response = await fetch('/api/auth/me', {
|
|
190
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
191
|
+
});
|
|
192
|
+
return response.json();
|
|
193
|
+
}
|
|
194
|
+
```
|
|
216
195
|
|
|
217
196
|
### 登录表单示例
|
|
218
197
|
|
|
219
198
|
```typescript
|
|
220
|
-
import type { IModule } from '@linyjs/client-module-interface';
|
|
221
199
|
import React, { useState } from 'react';
|
|
222
200
|
|
|
223
201
|
function LoginForm() {
|
|
@@ -226,34 +204,32 @@ function LoginForm() {
|
|
|
226
204
|
password: ''
|
|
227
205
|
});
|
|
228
206
|
const [error, setError] = useState('');
|
|
229
|
-
|
|
207
|
+
|
|
230
208
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
231
209
|
e.preventDefault();
|
|
232
|
-
|
|
210
|
+
|
|
233
211
|
try {
|
|
234
212
|
const response = await fetch('/api/auth/login', {
|
|
235
213
|
method: 'POST',
|
|
236
|
-
headers: {
|
|
237
|
-
'Content-Type': 'application/json'
|
|
238
|
-
},
|
|
214
|
+
headers: { 'Content-Type': 'application/json' },
|
|
239
215
|
body: JSON.stringify(formData)
|
|
240
216
|
});
|
|
241
|
-
|
|
217
|
+
|
|
242
218
|
const result = await response.json();
|
|
243
|
-
|
|
244
|
-
if (result.success) {
|
|
219
|
+
|
|
220
|
+
if (result.success !== false) {
|
|
245
221
|
// 保存 token 到 localStorage 或 cookie
|
|
246
222
|
localStorage.setItem('token', result.data.token);
|
|
247
223
|
// 跳转或刷新页面
|
|
248
224
|
window.location.reload();
|
|
249
225
|
} else {
|
|
250
|
-
setError(result.message);
|
|
226
|
+
setError(result.message || result.error);
|
|
251
227
|
}
|
|
252
228
|
} catch (err) {
|
|
253
229
|
setError('Network error');
|
|
254
230
|
}
|
|
255
231
|
};
|
|
256
|
-
|
|
232
|
+
|
|
257
233
|
return (
|
|
258
234
|
<form onSubmit={handleSubmit}>
|
|
259
235
|
<div>
|
|
@@ -279,110 +255,191 @@ function LoginForm() {
|
|
|
279
255
|
</form>
|
|
280
256
|
);
|
|
281
257
|
}
|
|
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
258
|
```
|
|
305
259
|
|
|
306
260
|
## 认证状态管理
|
|
307
261
|
|
|
308
|
-
###
|
|
262
|
+
### 创建并注册认证 Hook
|
|
263
|
+
|
|
264
|
+
在客户端模块中创建自定义 `useAuth` Hook,并通过 ServiceDef 注册到全局注册表,供其他模块通过 `useClientHook` 消费:
|
|
309
265
|
|
|
310
266
|
```typescript
|
|
311
267
|
import { useState, useEffect } from 'react';
|
|
268
|
+
import type { IModule, IHooksDef, IHooksService } from '@linyjs/client-module-interface';
|
|
312
269
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
email: string;
|
|
317
|
-
createdAt: Date;
|
|
318
|
-
roles?: string[];
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function useAuth() {
|
|
322
|
-
const [user, setUser] = useState<User | null>(null);
|
|
270
|
+
// 自定义认证 Hook
|
|
271
|
+
function useAuth() {
|
|
272
|
+
const [user, setUser] = useState<any>(null);
|
|
323
273
|
const [loading, setLoading] = useState(true);
|
|
324
|
-
|
|
274
|
+
|
|
325
275
|
useEffect(() => {
|
|
326
276
|
const token = localStorage.getItem('token');
|
|
327
|
-
|
|
277
|
+
|
|
328
278
|
if (!token) {
|
|
329
279
|
setLoading(false);
|
|
330
280
|
return;
|
|
331
281
|
}
|
|
332
|
-
|
|
282
|
+
|
|
333
283
|
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
|
-
}
|
|
284
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
345
285
|
})
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
286
|
+
.then(response => response.json())
|
|
287
|
+
.then(result => {
|
|
288
|
+
if (result.user) {
|
|
289
|
+
setUser(result.user);
|
|
290
|
+
} else {
|
|
291
|
+
localStorage.removeItem('token');
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
.finally(() => {
|
|
295
|
+
setLoading(false);
|
|
296
|
+
});
|
|
349
297
|
}, []);
|
|
350
|
-
|
|
298
|
+
|
|
351
299
|
const login = async (identifier: string, password: string) => {
|
|
352
300
|
const response = await fetch('/api/auth/login', {
|
|
353
301
|
method: 'POST',
|
|
354
|
-
headers: {
|
|
355
|
-
'Content-Type': 'application/json'
|
|
356
|
-
},
|
|
302
|
+
headers: { 'Content-Type': 'application/json' },
|
|
357
303
|
body: JSON.stringify({ identifier, password })
|
|
358
304
|
});
|
|
359
|
-
|
|
305
|
+
|
|
360
306
|
const result = await response.json();
|
|
361
|
-
|
|
362
|
-
if (result.success) {
|
|
307
|
+
|
|
308
|
+
if (result.success !== false) {
|
|
363
309
|
localStorage.setItem('token', result.data.token);
|
|
364
310
|
setUser(result.data.user);
|
|
365
311
|
return true;
|
|
366
312
|
}
|
|
367
|
-
|
|
313
|
+
|
|
368
314
|
return false;
|
|
369
315
|
};
|
|
370
|
-
|
|
316
|
+
|
|
371
317
|
const logout = () => {
|
|
372
318
|
localStorage.removeItem('token');
|
|
373
319
|
setUser(null);
|
|
374
320
|
};
|
|
375
|
-
|
|
321
|
+
|
|
376
322
|
const isAuthenticated = !!user;
|
|
377
|
-
|
|
378
|
-
return {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
323
|
+
|
|
324
|
+
return { user, loading, isAuthenticated, login, logout };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 通过 ServiceDef 注册 Hook
|
|
328
|
+
class AuthHooksService implements IHooksService {
|
|
329
|
+
hooks = {
|
|
330
|
+
useAuth,
|
|
384
331
|
};
|
|
385
332
|
}
|
|
333
|
+
|
|
334
|
+
const authHooksDef: IHooksDef = {
|
|
335
|
+
serviceTag: 'authHooks',
|
|
336
|
+
serviceImpl: AuthHooksService,
|
|
337
|
+
meta: { usageType: 'hook' },
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export const myClientModule: IModule = {
|
|
341
|
+
name: 'myClient',
|
|
342
|
+
serviceDefs: [authHooksDef],
|
|
343
|
+
};
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 跨模块使用认证 Hook
|
|
347
|
+
|
|
348
|
+
其他模块的组件通过 `useClientHook` 获取 `useAuth` Hook,无需直接导入:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { useClientHook } from '@linyjs/client-module-interface';
|
|
352
|
+
|
|
353
|
+
type UseAuth = () => {
|
|
354
|
+
user: any;
|
|
355
|
+
loading: boolean;
|
|
356
|
+
isAuthenticated: boolean;
|
|
357
|
+
login: (identifier: string, password: string) => Promise<boolean>;
|
|
358
|
+
logout: () => void;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
function NavBar() {
|
|
362
|
+
// 通过 useClientHook 从注册了 authHooks 的模块获取 useAuth Hook
|
|
363
|
+
const useAuth = useClientHook<UseAuth>('myClient.authHooks.useAuth');
|
|
364
|
+
|
|
365
|
+
if (!useAuth) {
|
|
366
|
+
return <div>认证功能加载中...</div>;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const { user, isAuthenticated, login, logout } = useAuth();
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<nav>
|
|
373
|
+
{isAuthenticated ? (
|
|
374
|
+
<>
|
|
375
|
+
<span>欢迎, {user.username}</span>
|
|
376
|
+
<button onClick={logout}>退出</button>
|
|
377
|
+
</>
|
|
378
|
+
) : (
|
|
379
|
+
<button onClick={() => login('admin', 'password')}>登录</button>
|
|
380
|
+
)}
|
|
381
|
+
</nav>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
> 💡 引用路径 `myClient.authHooks.useAuth` 遵循 `moduleName.serviceTag.hookName` 三段式格式。详见 [公共 Hooks 使用](./client-hooks.md)。
|
|
387
|
+
|
|
388
|
+
## 路由认证保护
|
|
389
|
+
|
|
390
|
+
### 在路由元数据中声明认证要求
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
class MyRouteService {
|
|
394
|
+
basePath = '/dashboard';
|
|
395
|
+
|
|
396
|
+
getRoutes() {
|
|
397
|
+
return [
|
|
398
|
+
{
|
|
399
|
+
path: '/',
|
|
400
|
+
component: Dashboard,
|
|
401
|
+
key: 'dashboard',
|
|
402
|
+
meta: {
|
|
403
|
+
requiresAuth: true, // 标记需要认证
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 路由守卫
|
|
412
|
+
|
|
413
|
+
> ⚠️ 路由守卫的 `canEnter` 回调不是 React 组件,不能使用 Hook。可直接调用认证 API:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
{
|
|
417
|
+
path: '/dashboard',
|
|
418
|
+
component: Dashboard,
|
|
419
|
+
key: 'dashboard',
|
|
420
|
+
guard: {
|
|
421
|
+
canEnter: async ({ location }) => {
|
|
422
|
+
const token = localStorage.getItem('token');
|
|
423
|
+
|
|
424
|
+
if (!token) {
|
|
425
|
+
return '/login'; // 未登录,重定向到登录页
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 调用认证 API 验证 token
|
|
429
|
+
const response = await fetch('/api/auth/me', {
|
|
430
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
431
|
+
});
|
|
432
|
+
const result = await response.json();
|
|
433
|
+
|
|
434
|
+
if (!result.user) {
|
|
435
|
+
localStorage.removeItem('token');
|
|
436
|
+
return '/login'; // token 无效,重定向到登录页
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return true; // 允许访问
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
386
443
|
```
|
|
387
444
|
|
|
388
445
|
## 最佳实践
|
|
@@ -398,7 +455,7 @@ export function useAuth() {
|
|
|
398
455
|
- 记录详细的错误日志用于调试
|
|
399
456
|
|
|
400
457
|
### 3. 安全性
|
|
401
|
-
-
|
|
458
|
+
- 始终在服务端通过 `auth` 中间件验证 token
|
|
402
459
|
- 不要在客户端存储敏感信息
|
|
403
460
|
- 使用强密码策略
|
|
404
461
|
- 考虑实现两步验证增强安全性
|
|
@@ -422,7 +479,23 @@ export function useAuth() {
|
|
|
422
479
|
- 确认用户账户已激活
|
|
423
480
|
- 检查数据库连接是否正常
|
|
424
481
|
|
|
425
|
-
3. **中间件无法获取用户信息**
|
|
426
|
-
- 确认 token
|
|
427
|
-
-
|
|
428
|
-
-
|
|
482
|
+
3. **中间件无法获取用户信息**
|
|
483
|
+
- 确认 token 在请求头中正确传递(`Authorization: Bearer <token>`)
|
|
484
|
+
- 确认路由的 `middlewares` 中正确引用了 `'auth'`
|
|
485
|
+
- 确认模块的 `moduleDependencies` 中声明了 `'authServer'`
|
|
486
|
+
|
|
487
|
+
4. **无法注入 UserService**
|
|
488
|
+
- 确认 ServiceDef 的 `dependencies` 中使用了 `'authServer.userService'`
|
|
489
|
+
- 确认模块的 `moduleDependencies` 中声明了 `'authServer'`
|
|
490
|
+
- 确认构造函数使用了解构赋值接收依赖
|
|
491
|
+
|
|
492
|
+
## 相关文档
|
|
493
|
+
|
|
494
|
+
- 📖 [共享模块接口](./shared-module-interface.md) — `IUserService` 等类型定义
|
|
495
|
+
- 🪝 [公共 Hooks 使用](./client-hooks.md) — `useClientHook` 获取跨模块 Hook
|
|
496
|
+
- 🛤️ [前端页面路由](./client-routes.md) — 路由守卫详细用法
|
|
497
|
+
- 🔐 [权限管理](./permissions.md) — 权限服务使用
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
**上一篇**: [数据持久化](./data-persistence.md) | **下一篇**: [权限管理](./permissions.md)
|