@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.
@@ -11,213 +11,191 @@ linyjs 提供完整的用户认证系统,支持:
11
11
  - 🛡️ 路由级别的认证保护
12
12
  - 👤 用户信息管理
13
13
 
14
- ## 配置认证服务
14
+ ## 内置认证服务
15
15
 
16
- ### 在服务端模块中注入依赖
16
+ linyjs 的 `authServer` 模块已内置完整的认证功能,包括:
17
17
 
18
- ```typescript
19
- import type { IModule, IServiceDef } from '@linyjs/server-module-interface';
20
- import { login, register, verifyToken } from '@linyjs/auth';
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
- class AuthController {
23
- // ... 控制器实现
24
- }
26
+ 其他模块**无需自行实现**认证逻辑,只需:
25
27
 
26
- const authControllerDef: IServiceDef = {
27
- serviceTag: 'authController',
28
- serviceImpl: AuthController,
29
- dependencies: [],
30
- meta: { usageType: 'controller' }
31
- };
28
+ 1. `moduleDependencies` 中声明 `'authServer'`
29
+ 2. 在路由的 `middlewares` 中引用 `'auth'` 中间件
30
+ 3. 需要用户管理时,通过 `dependencies` 注入 `IUserService`
32
31
 
33
- export const myServerModule: IModule = {
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 { register } from '@linyjs/auth';
37
+ import type { IControllerService, IControllerServiceDef, IModule } from '@linyjs/server-module-interface';
46
38
 
47
- class AuthController {
39
+ class ProtectedController implements IControllerService {
48
40
  getRoutes() {
49
41
  return [{
50
- method: 'POST' as const,
51
- path: '/api/auth/register',
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 (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
- }
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 { login } from '@linyjs/auth';
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
- 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
- };
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
- ### Token 验证
137
+ ### IUserService 接口
138
+
139
+ `IUserService` 通过 `@linyjs/shared-module-interface` 提供类型定义:
111
140
 
112
141
  ```typescript
113
- import { verifyToken, getUserById } from '@linyjs/auth';
142
+ interface IUserService {
143
+ /** 获取所有用户列表(不含密码哈希) */
144
+ getAllUsers(): Promise<ISafeUser[]>;
114
145
 
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
- }
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
- 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
- }
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
- 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
- }
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
- ### 创建认证 Hook
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
- 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);
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
- .finally(() => {
347
- setLoading(false);
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
- user,
380
- loading,
381
- isAuthenticated,
382
- login,
383
- logout
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
- - 始终在服务端验证 token
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
- - 检查 verifyToken 函数是否正常工作
428
- - 验证 getUserById 是否能找到对应用户
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)