@qlover/create-app 0.7.14 → 0.7.15
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/CHANGELOG.md +23 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/README.en.md +131 -0
- package/dist/templates/next-app/README.md +115 -20
- package/dist/templates/next-app/docs/en/api.md +387 -0
- package/dist/templates/next-app/docs/en/component.md +544 -0
- package/dist/templates/next-app/docs/en/database.md +496 -0
- package/dist/templates/next-app/docs/en/development-guide.md +727 -0
- package/dist/templates/next-app/docs/en/env.md +563 -0
- package/dist/templates/next-app/docs/en/i18n.md +287 -0
- package/dist/templates/next-app/docs/en/index.md +166 -0
- package/dist/templates/next-app/docs/en/page.md +457 -0
- package/dist/templates/next-app/docs/en/project-structure.md +177 -0
- package/dist/templates/next-app/docs/en/router.md +427 -0
- package/dist/templates/next-app/docs/en/theme.md +532 -0
- package/dist/templates/next-app/docs/en/validator.md +478 -0
- package/dist/templates/next-app/docs/zh/api.md +387 -0
- package/dist/templates/next-app/docs/zh/component.md +544 -0
- package/dist/templates/next-app/docs/zh/database.md +496 -0
- package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
- package/dist/templates/next-app/docs/zh/env.md +563 -0
- package/dist/templates/next-app/docs/zh/i18n.md +287 -0
- package/dist/templates/next-app/docs/zh/index.md +166 -0
- package/dist/templates/next-app/docs/zh/page.md +457 -0
- package/dist/templates/next-app/docs/zh/project-structure.md +177 -0
- package/dist/templates/next-app/docs/zh/router.md +427 -0
- package/dist/templates/next-app/docs/zh/theme.md +532 -0
- package/dist/templates/next-app/docs/zh/validator.md +476 -0
- package/package.json +1 -1
- package/dist/templates/next-app/docs/env.md +0 -94
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# 验证器开发指南
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
1. [验证器架构概述](#验证器架构概述)
|
|
6
|
+
2. [验证器接口和抽象层](#验证器接口和抽象层)
|
|
7
|
+
3. [具体验证器实现](#具体验证器实现)
|
|
8
|
+
4. [验证器使用示例](#验证器使用示例)
|
|
9
|
+
5. [自定义验证器示例](#自定义验证器示例)
|
|
10
|
+
6. [最佳实践和扩展](#最佳实践和扩展)
|
|
11
|
+
|
|
12
|
+
## 验证器架构概述
|
|
13
|
+
|
|
14
|
+
### 1. 整体架构
|
|
15
|
+
|
|
16
|
+
项目采用分层的验证器架构设计:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
应用层 验证层
|
|
20
|
+
┌──────────────┐ ┌──────────────┐
|
|
21
|
+
│ 业务服务 │ │ 验证器接口 │
|
|
22
|
+
├──────────────┤ ├──────────────┤
|
|
23
|
+
│ 参数处理 │ ◄─────┤ 验证器实现 │
|
|
24
|
+
├──────────────┤ ├──────────────┤
|
|
25
|
+
│ 错误处理 │ │ 验证规则 │
|
|
26
|
+
└──────────────┘ └──────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. 核心组件
|
|
30
|
+
|
|
31
|
+
- **验证器接口**:`ValidatorInterface`
|
|
32
|
+
- **验证器实现**:`LoginValidator`, `PaginationValidator` 等
|
|
33
|
+
- **验证规则**:使用 `zod` 库定义的验证规则
|
|
34
|
+
- **错误处理**:统一的错误响应格式
|
|
35
|
+
|
|
36
|
+
### 3. 验证结果类型
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// 验证失败结果
|
|
40
|
+
interface ValidationFaildResult {
|
|
41
|
+
path: PropertyKey[]; // 验证失败的字段路径
|
|
42
|
+
message: string; // 错误消息
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 验证器接口
|
|
46
|
+
interface ValidatorInterface {
|
|
47
|
+
// 验证数据并返回结果
|
|
48
|
+
validate(
|
|
49
|
+
data: unknown
|
|
50
|
+
): Promise<void | ValidationFaildResult> | void | ValidationFaildResult;
|
|
51
|
+
|
|
52
|
+
// 验证数据,成功返回数据,失败抛出错误
|
|
53
|
+
getThrow(data: unknown): unknown;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 验证器接口和抽象层
|
|
58
|
+
|
|
59
|
+
### 1. 基础验证器接口
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
export interface ValidatorInterface {
|
|
63
|
+
/**
|
|
64
|
+
* 验证数据并返回结果
|
|
65
|
+
* @param data - 要验证的数据
|
|
66
|
+
* @returns 如果验证通过返回 void,否则返回验证错误
|
|
67
|
+
*/
|
|
68
|
+
validate(
|
|
69
|
+
data: unknown
|
|
70
|
+
): Promise<void | ValidationFaildResult> | void | ValidationFaildResult;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 验证数据,成功返回数据,失败抛出错误
|
|
74
|
+
* @param data - 要验证的数据
|
|
75
|
+
* @returns 验证通过的数据
|
|
76
|
+
* @throws {ExecutorError} 如果数据无效,包含验证错误详情
|
|
77
|
+
*/
|
|
78
|
+
getThrow(data: unknown): unknown;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. 验证规则定义
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// 使用 zod 定义验证规则
|
|
86
|
+
const emailSchema = z.string().email({ message: V_EMAIL_INVALID });
|
|
87
|
+
|
|
88
|
+
const passwordSchema = z
|
|
89
|
+
.string()
|
|
90
|
+
.min(6, { message: V_PASSWORD_MIN_LENGTH })
|
|
91
|
+
.max(50, { message: V_PASSWORD_MAX_LENGTH })
|
|
92
|
+
.regex(/^\S+$/, { message: V_PASSWORD_SPECIAL_CHARS });
|
|
93
|
+
|
|
94
|
+
const pageSchema = z
|
|
95
|
+
.number()
|
|
96
|
+
.or(z.string())
|
|
97
|
+
.transform((val) => Number(val))
|
|
98
|
+
.refine((val) => val > 0, {
|
|
99
|
+
message: API_PAGE_INVALID
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 具体验证器实现
|
|
104
|
+
|
|
105
|
+
### 1. 登录验证器
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
@injectable()
|
|
109
|
+
export class LoginValidator implements ValidatorInterface {
|
|
110
|
+
// 验证邮箱
|
|
111
|
+
validateEmail(data: unknown): void | ValidationFaildResult {
|
|
112
|
+
const emailResult = emailSchema.safeParse(data);
|
|
113
|
+
if (!emailResult.success) {
|
|
114
|
+
return emailResult.error.issues[0];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 验证密码
|
|
119
|
+
validatePassword(data: unknown): void | ValidationFaildResult {
|
|
120
|
+
const passwordResult = passwordSchema.safeParse(data);
|
|
121
|
+
if (!passwordResult.success) {
|
|
122
|
+
return passwordResult.error.issues[0];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 实现验证接口
|
|
127
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
128
|
+
if (typeof data !== 'object' || data === null) {
|
|
129
|
+
return {
|
|
130
|
+
path: ['form'],
|
|
131
|
+
message: V_LOGIN_PARAMS_REQUIRED
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const { email, password } = data as Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
// 验证邮箱
|
|
138
|
+
let validateResult = this.validateEmail(email);
|
|
139
|
+
if (validateResult != null) {
|
|
140
|
+
return validateResult;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 验证密码
|
|
144
|
+
validateResult = this.validatePassword(password);
|
|
145
|
+
if (validateResult != null) {
|
|
146
|
+
return validateResult;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 实现 getThrow 接口
|
|
151
|
+
getThrow(data: unknown): LoginValidatorData {
|
|
152
|
+
const result = this.validate(data);
|
|
153
|
+
if (result == null) {
|
|
154
|
+
return data as LoginValidatorData;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const error: ExtendedExecutorError = new ExecutorError(result.message);
|
|
158
|
+
error.issues = [result];
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2. 分页验证器
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
@injectable()
|
|
168
|
+
export class PaginationValidator implements ValidatorInterface {
|
|
169
|
+
protected defaultPageSize = 10;
|
|
170
|
+
|
|
171
|
+
validatePageSize(value: unknown): void | ValidationFaildResult {
|
|
172
|
+
const result = pageSchema.safeParse(value);
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
return result.error.issues[0];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
179
|
+
if (typeof data !== 'object' || data === null) {
|
|
180
|
+
return {
|
|
181
|
+
path: ['form'],
|
|
182
|
+
message: 'form is required'
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return this.validatePageSize((data as Record<string, unknown>).page);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getThrow(data: unknown): PaginationInterface {
|
|
190
|
+
const result = this.validate(data);
|
|
191
|
+
if (result) {
|
|
192
|
+
throw new Error(result.message);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { page: 1, pageSize: this.defaultPageSize };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 验证器使用示例
|
|
201
|
+
|
|
202
|
+
### 1. 在 API 路由中使用
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
export async function POST(req: NextRequest) {
|
|
206
|
+
const server = new BootstrapServer();
|
|
207
|
+
|
|
208
|
+
const result = await server.execNoError(async ({ parameters: { IOC } }) => {
|
|
209
|
+
// 获取请求数据
|
|
210
|
+
const data = await req.json();
|
|
211
|
+
|
|
212
|
+
// 使用验证器验证数据
|
|
213
|
+
const loginValidator = IOC(LoginValidator);
|
|
214
|
+
const validatedData = loginValidator.getThrow(data);
|
|
215
|
+
|
|
216
|
+
// 使用验证后的数据
|
|
217
|
+
const userService = IOC(UserService);
|
|
218
|
+
return userService.login(validatedData);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (result instanceof ExecutorError) {
|
|
222
|
+
return NextResponse.json(new AppErrorApi(result.id, result.message));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return NextResponse.json(new AppSuccessApi(result));
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 2. 在服务中使用
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
@injectable()
|
|
233
|
+
export class UserService {
|
|
234
|
+
constructor(
|
|
235
|
+
@inject(LoginValidator) private loginValidator: LoginValidator,
|
|
236
|
+
@inject(UserRepository) private userRepository: UserRepositoryInterface
|
|
237
|
+
) {}
|
|
238
|
+
|
|
239
|
+
async validateAndCreateUser(data: unknown): Promise<User> {
|
|
240
|
+
// 验证数据
|
|
241
|
+
const validatedData = this.loginValidator.getThrow(data);
|
|
242
|
+
|
|
243
|
+
// 使用验证后的数据
|
|
244
|
+
return this.userRepository.add(validatedData);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 自定义验证器示例
|
|
250
|
+
|
|
251
|
+
### 1. 创建自定义验证规则
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// 定义自定义验证规则
|
|
255
|
+
const phoneSchema = z
|
|
256
|
+
.string()
|
|
257
|
+
.regex(/^1[3-9]\d{9}$/, { message: '手机号格式不正确' });
|
|
258
|
+
|
|
259
|
+
const addressSchema = z.object({
|
|
260
|
+
province: z.string().min(1, '省份不能为空'),
|
|
261
|
+
city: z.string().min(1, '城市不能为空'),
|
|
262
|
+
detail: z.string().min(1, '详细地址不能为空')
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// 创建用户信息验证器
|
|
266
|
+
@injectable()
|
|
267
|
+
export class UserInfoValidator implements ValidatorInterface {
|
|
268
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
269
|
+
if (typeof data !== 'object' || data === null) {
|
|
270
|
+
return {
|
|
271
|
+
path: ['form'],
|
|
272
|
+
message: '表单数据不能为空'
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const { phone, address } = data as Record<string, unknown>;
|
|
277
|
+
|
|
278
|
+
// 验证手机号
|
|
279
|
+
const phoneResult = phoneSchema.safeParse(phone);
|
|
280
|
+
if (!phoneResult.success) {
|
|
281
|
+
return phoneResult.error.issues[0];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 验证地址
|
|
285
|
+
const addressResult = addressSchema.safeParse(address);
|
|
286
|
+
if (!addressResult.success) {
|
|
287
|
+
return addressResult.error.issues[0];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getThrow(data: unknown): UserInfoData {
|
|
292
|
+
const result = this.validate(data);
|
|
293
|
+
if (result) {
|
|
294
|
+
throw new ExecutorError(result.message);
|
|
295
|
+
}
|
|
296
|
+
return data as UserInfoData;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 2. 组合多个验证器
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
@injectable()
|
|
305
|
+
export class UserProfileValidator implements ValidatorInterface {
|
|
306
|
+
constructor(
|
|
307
|
+
@inject(UserInfoValidator) private userInfoValidator: UserInfoValidator,
|
|
308
|
+
@inject(LoginValidator) private loginValidator: LoginValidator
|
|
309
|
+
) {}
|
|
310
|
+
|
|
311
|
+
async validate(data: unknown): Promise<void | ValidationFaildResult> {
|
|
312
|
+
// 验证登录信息
|
|
313
|
+
const loginResult = this.loginValidator.validate(data);
|
|
314
|
+
if (loginResult) return loginResult;
|
|
315
|
+
|
|
316
|
+
// 验证用户信息
|
|
317
|
+
const userInfoResult = this.userInfoValidator.validate(data);
|
|
318
|
+
if (userInfoResult) return userInfoResult;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async getThrow(data: unknown): Promise<UserProfileData> {
|
|
322
|
+
const result = await this.validate(data);
|
|
323
|
+
if (result) {
|
|
324
|
+
throw new ExecutorError(result.message);
|
|
325
|
+
}
|
|
326
|
+
return data as UserProfileData;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## 最佳实践和扩展
|
|
332
|
+
|
|
333
|
+
### 1. 验证器组织
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// 1. 按功能模块组织验证器
|
|
337
|
+
src / validators / user / LoginValidator.ts;
|
|
338
|
+
ProfileValidator.ts;
|
|
339
|
+
SettingsValidator.ts;
|
|
340
|
+
product / CreateValidator.ts;
|
|
341
|
+
UpdateValidator.ts;
|
|
342
|
+
order / PlaceOrderValidator.ts;
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 2. 验证规则复用
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// 1. 创建共享验证规则
|
|
349
|
+
const commonRules = {
|
|
350
|
+
email: z.string().email({ message: '邮箱格式不正确' }),
|
|
351
|
+
phone: z.string().regex(/^1[3-9]\d{9}$/, { message: '手机号格式不正确' }),
|
|
352
|
+
password: z
|
|
353
|
+
.string()
|
|
354
|
+
.min(6, '密码至少6位')
|
|
355
|
+
.max(50, '密码最多50位')
|
|
356
|
+
.regex(/^\S+$/, '密码不能包含空格')
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// 2. 在验证器中复用
|
|
360
|
+
export class UserValidator implements ValidatorInterface {
|
|
361
|
+
validate(data: unknown): void | ValidationFaildResult {
|
|
362
|
+
const schema = z.object({
|
|
363
|
+
email: commonRules.email,
|
|
364
|
+
password: commonRules.password,
|
|
365
|
+
phone: commonRules.phone.optional()
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const result = schema.safeParse(data);
|
|
369
|
+
if (!result.success) {
|
|
370
|
+
return result.error.issues[0];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 3. 错误处理增强
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// 1. 创建验证错误基类
|
|
380
|
+
export class ValidationError extends Error {
|
|
381
|
+
constructor(
|
|
382
|
+
public readonly path: PropertyKey[],
|
|
383
|
+
message: string
|
|
384
|
+
) {
|
|
385
|
+
super(message);
|
|
386
|
+
this.name = 'ValidationError';
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 2. 创建字段验证错误
|
|
391
|
+
export class FieldValidationError extends ValidationError {
|
|
392
|
+
constructor(
|
|
393
|
+
public readonly field: string,
|
|
394
|
+
message: string
|
|
395
|
+
) {
|
|
396
|
+
super([field], message);
|
|
397
|
+
this.name = 'FieldValidationError';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 3. 在验证器中使用
|
|
402
|
+
export class EnhancedValidator implements ValidatorInterface {
|
|
403
|
+
getThrow(data: unknown): unknown {
|
|
404
|
+
const result = this.validate(data);
|
|
405
|
+
if (result) {
|
|
406
|
+
throw new FieldValidationError(result.path.join('.'), result.message);
|
|
407
|
+
}
|
|
408
|
+
return data;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### 4. 异步验证支持
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
@injectable()
|
|
417
|
+
export class AsyncValidator implements ValidatorInterface {
|
|
418
|
+
constructor(
|
|
419
|
+
@inject(UserRepository) private userRepository: UserRepositoryInterface
|
|
420
|
+
) {}
|
|
421
|
+
|
|
422
|
+
async validate(data: unknown): Promise<void | ValidationFaildResult> {
|
|
423
|
+
// 基本验证
|
|
424
|
+
const basicResult = this.validateBasicFields(data);
|
|
425
|
+
if (basicResult) return basicResult;
|
|
426
|
+
|
|
427
|
+
// 异步验证(例如检查邮箱是否已存在)
|
|
428
|
+
const { email } = data as { email: string };
|
|
429
|
+
const existingUser = await this.userRepository.getUserByEmail(email);
|
|
430
|
+
|
|
431
|
+
if (existingUser) {
|
|
432
|
+
return {
|
|
433
|
+
path: ['email'],
|
|
434
|
+
message: '该邮箱已被注册'
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async getThrow(data: unknown): Promise<unknown> {
|
|
440
|
+
const result = await this.validate(data);
|
|
441
|
+
if (result) {
|
|
442
|
+
throw new ValidationError(result.path, result.message);
|
|
443
|
+
}
|
|
444
|
+
return data;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## 总结
|
|
450
|
+
|
|
451
|
+
项目的验证器设计遵循以下原则:
|
|
452
|
+
|
|
453
|
+
1. **接口抽象**:
|
|
454
|
+
- 清晰的验证器接口定义
|
|
455
|
+
- 统一的验证结果格式
|
|
456
|
+
- 可扩展的验证器实现
|
|
457
|
+
|
|
458
|
+
2. **类型安全**:
|
|
459
|
+
- 使用 zod 提供类型安全的验证规则
|
|
460
|
+
- TypeScript 类型定义
|
|
461
|
+
- 运行时类型检查
|
|
462
|
+
|
|
463
|
+
3. **错误处理**:
|
|
464
|
+
- 统一的错误响应格式
|
|
465
|
+
- 详细的错误信息
|
|
466
|
+
- 错误链路追踪
|
|
467
|
+
|
|
468
|
+
4. **可扩展性**:
|
|
469
|
+
- 支持自定义验证规则
|
|
470
|
+
- 支持验证器组合
|
|
471
|
+
- 支持异步验证
|
|
472
|
+
|
|
473
|
+
5. **最佳实践**:
|
|
474
|
+
- 验证规则复用
|
|
475
|
+
- 模块化组织
|
|
476
|
+
- 错误处理增强
|
package/package.json
CHANGED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# 环境配置指南
|
|
2
|
-
|
|
3
|
-
本项目使用 Next.js 内置的环境变量系统来管理不同环境的配置。
|
|
4
|
-
|
|
5
|
-
## 环境文件
|
|
6
|
-
|
|
7
|
-
项目支持以下环境配置文件:
|
|
8
|
-
|
|
9
|
-
- `.env` - 默认环境配置,适用于所有环境
|
|
10
|
-
- `.env.local` - 本地环境配置,会覆盖 `.env`(不应提交到版本控制)
|
|
11
|
-
- `.env.development` - 开发环境配置
|
|
12
|
-
- `.env.production` - 生产环境配置
|
|
13
|
-
- `.env.test` - 测试环境配置
|
|
14
|
-
|
|
15
|
-
加载优先级(从高到低):
|
|
16
|
-
|
|
17
|
-
1. `.env.local`
|
|
18
|
-
2. `.env.[environment]`
|
|
19
|
-
3. `.env`
|
|
20
|
-
|
|
21
|
-
## 使用方法
|
|
22
|
-
|
|
23
|
-
1. 复制 `.env.template` 创建对应环境的配置文件:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
# 开发环境
|
|
27
|
-
cp .env.template .env.development
|
|
28
|
-
|
|
29
|
-
# 生产环境
|
|
30
|
-
cp .env.template .env.production
|
|
31
|
-
|
|
32
|
-
# 测试环境
|
|
33
|
-
cp .env.template .env.test
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
2. 修改对应环境的配置文件内容
|
|
37
|
-
|
|
38
|
-
3. 启动应用时指定环境:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# 开发环境
|
|
42
|
-
APP_ENV=development npm run dev
|
|
43
|
-
|
|
44
|
-
# 生产环境
|
|
45
|
-
APP_ENV=production npm run build
|
|
46
|
-
APP_ENV=production npm run start
|
|
47
|
-
|
|
48
|
-
# 测试环境
|
|
49
|
-
APP_ENV=test npm run test
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## 在代码中使用环境变量
|
|
53
|
-
|
|
54
|
-
1. 服务端和客户端都可访问的变量:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// 在 .env 文件中定义
|
|
58
|
-
PUBLIC_API_URL=http://api.example.com
|
|
59
|
-
|
|
60
|
-
// 在代码中使用
|
|
61
|
-
console.log(process.env.PUBLIC_API_URL)
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
2. 仅服务端可访问的变量:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// 在 .env 文件中定义(以 PRIVATE_ 开头)
|
|
68
|
-
PRIVATE_API_KEY = secret_key;
|
|
69
|
-
|
|
70
|
-
// 在服务端代码中使用
|
|
71
|
-
console.log(process.env.PRIVATE_API_KEY);
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
3. 使用运行时配置:
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
import getConfig from 'next/config';
|
|
78
|
-
|
|
79
|
-
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
|
|
80
|
-
|
|
81
|
-
// 服务端配置
|
|
82
|
-
console.log(serverRuntimeConfig.mySecret);
|
|
83
|
-
|
|
84
|
-
// 公共配置
|
|
85
|
-
console.log(publicRuntimeConfig.appEnv);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## 注意事项
|
|
89
|
-
|
|
90
|
-
1. 不要将包含敏感信息的 `.env` 文件提交到版本控制
|
|
91
|
-
2. 确保 `.env.local` 和 `*.env` 文件在 `.gitignore` 中
|
|
92
|
-
3. 使用 `.env.template` 作为配置模板
|
|
93
|
-
4. 环境变量命名推荐使用大写字母和下划线
|
|
94
|
-
5. 在 CI/CD 中使用环境变量时,直接在平台中配置,不要使用 .env 文件
|