@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,563 @@
|
|
|
1
|
+
# Next.js 环境变量配置指南
|
|
2
|
+
|
|
3
|
+
## 什么是环境变量?
|
|
4
|
+
|
|
5
|
+
环境变量是一种在不同环境(开发、测试、生产)中配置应用行为的方式。在 Next.js 中,环境变量的使用有其特殊性,尤其是在客户端和服务端的处理上。
|
|
6
|
+
|
|
7
|
+
**关键特点**:
|
|
8
|
+
|
|
9
|
+
- 客户端环境变量必须以 `NEXT_PUBLIC_` 开头
|
|
10
|
+
- 服务端环境变量可以直接使用,无需特殊前缀
|
|
11
|
+
- 支持多环境配置和本地覆盖
|
|
12
|
+
|
|
13
|
+
## 环境变量加载优先级
|
|
14
|
+
|
|
15
|
+
Next.js 按照以下优先级加载环境变量:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
.env.local → .env.development/.env.production → .env
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 文件结构
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
项目根目录/
|
|
25
|
+
├── .env # 默认环境变量
|
|
26
|
+
├── .env.local # 本地环境变量(git ignored)
|
|
27
|
+
├── .env.development # 开发环境变量
|
|
28
|
+
├── .env.production # 生产环境变量
|
|
29
|
+
├── .env.test # 测试环境变量
|
|
30
|
+
└── src/
|
|
31
|
+
└── base/
|
|
32
|
+
└── cases/
|
|
33
|
+
└── AppConfig.ts # 应用配置类
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 环境变量文件
|
|
37
|
+
|
|
38
|
+
### 1. 环境变量文件示例
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# .env (默认配置)
|
|
42
|
+
# 客户端可访问的环境变量(以 NEXT_PUBLIC_ 开头)
|
|
43
|
+
NEXT_PUBLIC_APP_NAME=MyApp
|
|
44
|
+
NEXT_PUBLIC_API_BASE_URL=http://api.example.com
|
|
45
|
+
NEXT_PUBLIC_ENABLE_ANALYTICS=false
|
|
46
|
+
|
|
47
|
+
# 仅服务端可访问的环境变量
|
|
48
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/db
|
|
49
|
+
JWT_SECRET=your-secret-key
|
|
50
|
+
API_TOKEN=server-side-token
|
|
51
|
+
|
|
52
|
+
# .env.development (开发环境)
|
|
53
|
+
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/api
|
|
54
|
+
NEXT_PUBLIC_DEBUG=true
|
|
55
|
+
DATABASE_URL=postgres://dev:pass@localhost:5432/dev_db
|
|
56
|
+
|
|
57
|
+
# .env.production (生产环境)
|
|
58
|
+
NEXT_PUBLIC_API_BASE_URL=https://api.production.com
|
|
59
|
+
NEXT_PUBLIC_DEBUG=false
|
|
60
|
+
DATABASE_URL=postgres://prod:pass@production:5432/prod_db
|
|
61
|
+
|
|
62
|
+
# .env.local (本地覆盖,不提交到 git)
|
|
63
|
+
NEXT_PUBLIC_FEATURE_FLAG=true
|
|
64
|
+
DATABASE_URL=postgres://local:pass@localhost:5432/local_db
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. 环境变量使用规范
|
|
68
|
+
|
|
69
|
+
#### 客户端环境变量
|
|
70
|
+
|
|
71
|
+
- 必须以 `NEXT_PUBLIC_` 开头
|
|
72
|
+
- 会被打包到客户端代码中
|
|
73
|
+
- 适用于公开的配置信息
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# ✅ 正确的客户端环境变量
|
|
77
|
+
NEXT_PUBLIC_API_URL=https://api.example.com
|
|
78
|
+
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=UA-XXXXX
|
|
79
|
+
NEXT_PUBLIC_FEATURE_FLAGS={"newUI":true}
|
|
80
|
+
|
|
81
|
+
# ❌ 错误的客户端环境变量(没有 NEXT_PUBLIC_ 前缀)
|
|
82
|
+
API_URL=https://api.example.com # 客户端无法访问
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### 服务端环境变量
|
|
86
|
+
|
|
87
|
+
- 无需特殊前缀
|
|
88
|
+
- 只在服务端可用
|
|
89
|
+
- 适用于敏感信息
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# ✅ 正确的服务端环境变量
|
|
93
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/db
|
|
94
|
+
JWT_SECRET=your-secret-key
|
|
95
|
+
API_TOKEN=your-api-token
|
|
96
|
+
|
|
97
|
+
# ❌ 不应该以 NEXT_PUBLIC_ 开头的服务端敏感信息
|
|
98
|
+
NEXT_PUBLIC_DATABASE_URL=postgres://user:pass@localhost:5432/db # 安全风险!
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 项目中的实现
|
|
102
|
+
|
|
103
|
+
### 1. 应用配置类
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// src/base/cases/AppConfig.ts
|
|
107
|
+
export class AppConfig implements EnvConfigInterface {
|
|
108
|
+
/**
|
|
109
|
+
* 应用名称
|
|
110
|
+
* @description 从 NEXT_PUBLIC_APP_NAME 环境变量获取
|
|
111
|
+
*/
|
|
112
|
+
readonly appName = process.env.NEXT_PUBLIC_APP_NAME || '';
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* API 基础 URL
|
|
116
|
+
* @description 从 NEXT_PUBLIC_API_BASE_URL 环境变量获取
|
|
117
|
+
*/
|
|
118
|
+
readonly apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || '';
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 是否为开发环境
|
|
122
|
+
*/
|
|
123
|
+
readonly isDevelopment = process.env.NODE_ENV === 'development';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 是否为生产环境
|
|
127
|
+
*/
|
|
128
|
+
readonly isProduction = process.env.NODE_ENV === 'production';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 数据库连接 URL(仅服务端可用)
|
|
132
|
+
*/
|
|
133
|
+
readonly databaseUrl = process.env.DATABASE_URL;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* JWT 密钥(仅服务端可用)
|
|
137
|
+
*/
|
|
138
|
+
readonly jwtSecret = process.env.JWT_SECRET;
|
|
139
|
+
|
|
140
|
+
// ... 更多配置项
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2. 在客户端使用配置
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// 在组件中使用
|
|
148
|
+
function Analytics() {
|
|
149
|
+
// ✅ 正确:通过 IOC 获取配置对象
|
|
150
|
+
const appConfig = IOC(IOCIdentifier.AppConfig);
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (appConfig.analyticsId) {
|
|
154
|
+
// 初始化 analytics
|
|
155
|
+
}
|
|
156
|
+
}, [appConfig.analyticsId]);
|
|
157
|
+
|
|
158
|
+
return appConfig.debug ? <DebugInfo /> : null;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 3. 在服务端使用配置
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// app/api/auth/[...nextauth]/route.ts
|
|
166
|
+
import { NextResponse } from 'next/server';
|
|
167
|
+
|
|
168
|
+
export async function GET() {
|
|
169
|
+
// ✅ 正确:通过 IOC 获取配置对象
|
|
170
|
+
const appConfig = IOC(IOCIdentifier.AppConfig);
|
|
171
|
+
|
|
172
|
+
if (!appConfig.databaseUrl || !appConfig.jwtSecret) {
|
|
173
|
+
return NextResponse.json(
|
|
174
|
+
{ error: 'Server configuration error' },
|
|
175
|
+
{ status: 500 }
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 使用配置...
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 4. 在服务中使用配置
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// 定义服务类
|
|
187
|
+
@injectable()
|
|
188
|
+
export class AdminService {
|
|
189
|
+
constructor(
|
|
190
|
+
@inject(IOCIdentifier.AppConfig)
|
|
191
|
+
private appConfig: AppConfig
|
|
192
|
+
) {}
|
|
193
|
+
|
|
194
|
+
async fetchAdminData() {
|
|
195
|
+
const response = await fetch('https://api.example.com/admin', {
|
|
196
|
+
headers: {
|
|
197
|
+
Authorization: `Bearer ${this.appConfig.apiToken}`
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return response.json();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 最佳实践
|
|
206
|
+
|
|
207
|
+
### 1. 环境变量命名规范
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# ✅ 好的命名
|
|
211
|
+
NEXT_PUBLIC_APP_NAME=MyApp
|
|
212
|
+
NEXT_PUBLIC_API_URL=https://api.example.com
|
|
213
|
+
NEXT_PUBLIC_FEATURE_FLAGS={"darkMode":true}
|
|
214
|
+
|
|
215
|
+
# ❌ 不好的命名
|
|
216
|
+
next_public_app_name=MyApp # 应该使用大写
|
|
217
|
+
NEXT_PUBLIC_SECRET_KEY=xxx # 敏感信息不应该用 NEXT_PUBLIC_
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 2. 配置类实现
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// 定义配置接口
|
|
224
|
+
interface EnvConfigInterface {
|
|
225
|
+
readonly env: string;
|
|
226
|
+
readonly appName: string;
|
|
227
|
+
readonly appVersion: string;
|
|
228
|
+
// ... 其他配置项
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 实现配置类
|
|
232
|
+
@injectable()
|
|
233
|
+
export class AppConfig implements EnvConfigInterface {
|
|
234
|
+
/**
|
|
235
|
+
* 当前环境模式
|
|
236
|
+
* @description 基于当前使用的 .env 文件自动设置
|
|
237
|
+
*/
|
|
238
|
+
readonly env: string = process.env.APP_ENV!;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 应用名称
|
|
242
|
+
*/
|
|
243
|
+
readonly appName: string = name;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 应用版本
|
|
247
|
+
*/
|
|
248
|
+
readonly appVersion: string = version;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 用户令牌存储键
|
|
252
|
+
*/
|
|
253
|
+
readonly userTokenKey: string = '_user_token';
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 数据库连接 URL(仅服务端)
|
|
257
|
+
*/
|
|
258
|
+
readonly supabaseUrl: string = process.env.SUPABASE_URL!;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 数据库匿名密钥(仅服务端)
|
|
262
|
+
*/
|
|
263
|
+
readonly supabaseAnonKey: string = process.env.SUPABASE_ANON_KEY!;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* JWT 密钥(仅服务端)
|
|
267
|
+
*/
|
|
268
|
+
readonly jwtSecret: string = process.env.JWT_SECRET!;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* JWT 过期时间
|
|
272
|
+
* @example '30 days'
|
|
273
|
+
* @example '1 year'
|
|
274
|
+
*/
|
|
275
|
+
readonly jwtExpiresIn: StringValue = '30 days';
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* OpenAI API 配置(仅服务端)
|
|
279
|
+
*/
|
|
280
|
+
readonly openaiBaseUrl: string = process.env.CEREBRAS_BASE_URL!;
|
|
281
|
+
readonly openaiApiKey: string = process.env.CEREBRAS_API_KEY!;
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 3. 配置验证
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// 定义配置验证器
|
|
289
|
+
@injectable()
|
|
290
|
+
export class ConfigValidator {
|
|
291
|
+
constructor(
|
|
292
|
+
@inject(IOCIdentifier.AppConfig)
|
|
293
|
+
private appConfig: AppConfig,
|
|
294
|
+
@inject(IOCIdentifier.Logger)
|
|
295
|
+
private logger: LoggerInterface
|
|
296
|
+
) {}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 验证所有必需的配置项
|
|
300
|
+
* @throws {Error} 当必需的配置项缺失时抛出错误
|
|
301
|
+
*/
|
|
302
|
+
validateRequiredConfig(): void {
|
|
303
|
+
// 验证基础配置
|
|
304
|
+
this.validateBasicConfig();
|
|
305
|
+
|
|
306
|
+
// 根据运行环境验证不同的配置
|
|
307
|
+
if (typeof window === 'undefined') {
|
|
308
|
+
this.validateServerConfig();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 验证基础配置项
|
|
314
|
+
*/
|
|
315
|
+
private validateBasicConfig(): void {
|
|
316
|
+
const requiredConfigs: Array<keyof AppConfig> = [
|
|
317
|
+
'env',
|
|
318
|
+
'appName',
|
|
319
|
+
'appVersion',
|
|
320
|
+
'userTokenKey'
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
for (const key of requiredConfigs) {
|
|
324
|
+
if (!this.appConfig[key]) {
|
|
325
|
+
throw new Error(`Missing required config: ${key}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 验证服务端配置项
|
|
332
|
+
*/
|
|
333
|
+
private validateServerConfig(): void {
|
|
334
|
+
const requiredServerConfigs: Array<keyof AppConfig> = [
|
|
335
|
+
'supabaseUrl',
|
|
336
|
+
'supabaseAnonKey',
|
|
337
|
+
'jwtSecret',
|
|
338
|
+
'openaiBaseUrl',
|
|
339
|
+
'openaiApiKey'
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
for (const key of requiredServerConfigs) {
|
|
343
|
+
if (!this.appConfig[key]) {
|
|
344
|
+
throw new Error(`Missing required server config: ${key}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 验证配置值的格式
|
|
351
|
+
*/
|
|
352
|
+
validateConfigFormat(): void {
|
|
353
|
+
// 验证 URL 格式
|
|
354
|
+
if (!this.isValidUrl(this.appConfig.supabaseUrl)) {
|
|
355
|
+
throw new Error('Invalid supabaseUrl format');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 验证 JWT 过期时间格式
|
|
359
|
+
if (!this.isValidDuration(this.appConfig.jwtExpiresIn)) {
|
|
360
|
+
throw new Error('Invalid jwtExpiresIn format');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.logger.info('All config formats validated successfully');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private isValidUrl(url: string): boolean {
|
|
367
|
+
try {
|
|
368
|
+
new URL(url);
|
|
369
|
+
return true;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private isValidDuration(duration: string): boolean {
|
|
376
|
+
// 实现持续时间格式验证逻辑
|
|
377
|
+
return /^\d+\s+(days?|weeks?|months?|years?)$/.test(duration);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 4. 敏感信息处理
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
# .env.local (不提交到 git)
|
|
386
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/db
|
|
387
|
+
JWT_SECRET=your-secret-key
|
|
388
|
+
API_TOKEN=your-api-token
|
|
389
|
+
|
|
390
|
+
# .env.template (提交到 git,作为模板)
|
|
391
|
+
DATABASE_URL=postgres://user:password@localhost:5432/dbname
|
|
392
|
+
JWT_SECRET=your-jwt-secret-here
|
|
393
|
+
API_TOKEN=your-api-token-here
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## 调试和故障排除
|
|
397
|
+
|
|
398
|
+
### 1. 配置调试工具
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
@injectable()
|
|
402
|
+
export class ConfigDebugger {
|
|
403
|
+
constructor(
|
|
404
|
+
@inject(IOCIdentifier.AppConfig)
|
|
405
|
+
private appConfig: AppConfig,
|
|
406
|
+
@inject(IOCIdentifier.Logger)
|
|
407
|
+
private logger: LoggerInterface
|
|
408
|
+
) {}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* 打印配置信息
|
|
412
|
+
*/
|
|
413
|
+
logConfig(): void {
|
|
414
|
+
this.logger.group('Configuration Debug Info');
|
|
415
|
+
|
|
416
|
+
// 基础配置
|
|
417
|
+
this.logger.info('Basic Config:', {
|
|
418
|
+
env: this.appConfig.env,
|
|
419
|
+
appName: this.appConfig.appName,
|
|
420
|
+
appVersion: this.appConfig.appVersion
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// 如果在服务端,打印服务端配置
|
|
424
|
+
if (typeof window === 'undefined') {
|
|
425
|
+
this.logger.info('Server Config:', {
|
|
426
|
+
supabaseUrl: this.maskSensitiveInfo(this.appConfig.supabaseUrl),
|
|
427
|
+
jwtExpiresIn: this.appConfig.jwtExpiresIn
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
this.logger.groupEnd();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 验证配置健康状态
|
|
436
|
+
*/
|
|
437
|
+
async checkConfigHealth(): Promise<void> {
|
|
438
|
+
try {
|
|
439
|
+
// 验证数据库连接
|
|
440
|
+
if (typeof window === 'undefined') {
|
|
441
|
+
await this.checkDatabaseConnection();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// 验证其他配置项
|
|
445
|
+
this.validateConfigValues();
|
|
446
|
+
|
|
447
|
+
this.logger.info('Configuration health check passed');
|
|
448
|
+
} catch (error) {
|
|
449
|
+
this.logger.error('Configuration health check failed:', error);
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private async checkDatabaseConnection(): Promise<void> {
|
|
455
|
+
// 实现数据库连接检查逻辑
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private validateConfigValues(): void {
|
|
459
|
+
// 实现配置值验证逻辑
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private maskSensitiveInfo(value: string): string {
|
|
463
|
+
return value.replace(/^(https?:\/\/[^:]+:)([^@]+)(@.*)$/, '$1****$3');
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### 2. 常见问题处理
|
|
469
|
+
|
|
470
|
+
**问题 1:配置未正确注入**
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// ❌ 错误:直接使用环境变量
|
|
474
|
+
class UserService {
|
|
475
|
+
private apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ✅ 正确:通过配置类获取
|
|
479
|
+
@injectable()
|
|
480
|
+
class UserService {
|
|
481
|
+
constructor(
|
|
482
|
+
@inject(IOCIdentifier.AppConfig)
|
|
483
|
+
private appConfig: AppConfig
|
|
484
|
+
) {}
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**问题 2:配置验证失败**
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
// ❌ 错误:没有配置验证
|
|
492
|
+
@injectable()
|
|
493
|
+
class ApiService {
|
|
494
|
+
constructor(
|
|
495
|
+
@inject(IOCIdentifier.AppConfig)
|
|
496
|
+
private appConfig: AppConfig
|
|
497
|
+
) {}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ✅ 正确:包含配置验证
|
|
501
|
+
@injectable()
|
|
502
|
+
class ApiService {
|
|
503
|
+
constructor(
|
|
504
|
+
@inject(IOCIdentifier.AppConfig)
|
|
505
|
+
private appConfig: AppConfig,
|
|
506
|
+
@inject(IOCIdentifier.ConfigValidator)
|
|
507
|
+
private configValidator: ConfigValidator
|
|
508
|
+
) {
|
|
509
|
+
this.configValidator.validateRequiredConfig();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**问题 3:配置类型处理**
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
// ❌ 错误:手动类型转换
|
|
518
|
+
class FeatureService {
|
|
519
|
+
private isDebug = process.env.NEXT_PUBLIC_DEBUG === 'true';
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ✅ 正确:在配置类中处理类型转换
|
|
523
|
+
@injectable()
|
|
524
|
+
export class AppConfig implements EnvConfigInterface {
|
|
525
|
+
readonly debug: boolean = this.parseBoolean(process.env.NEXT_PUBLIC_DEBUG);
|
|
526
|
+
|
|
527
|
+
private parseBoolean(value: string | undefined): boolean {
|
|
528
|
+
return value?.toLowerCase() === 'true';
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## 总结
|
|
534
|
+
|
|
535
|
+
面向对象的配置管理系统提供了:
|
|
536
|
+
|
|
537
|
+
1. **配置封装**:
|
|
538
|
+
- 通过 `AppConfig` 类统一管理所有配置
|
|
539
|
+
- 实现 `EnvConfigInterface` 接口确保配置完整性
|
|
540
|
+
- 使用依赖注入实现配置的解耦
|
|
541
|
+
|
|
542
|
+
2. **类型安全**:
|
|
543
|
+
- 配置类中处理类型转换
|
|
544
|
+
- TypeScript 接口定义确保类型正确
|
|
545
|
+
- 编译时类型检查
|
|
546
|
+
|
|
547
|
+
3. **配置验证**:
|
|
548
|
+
- 专门的 `ConfigValidator` 类处理配置验证
|
|
549
|
+
- 运行时配置完整性检查
|
|
550
|
+
- 配置格式验证
|
|
551
|
+
|
|
552
|
+
4. **最佳实践**:
|
|
553
|
+
- 依赖注入管理配置依赖
|
|
554
|
+
- 配置验证和调试工具
|
|
555
|
+
- 敏感信息保护
|
|
556
|
+
- 类型安全处理
|
|
557
|
+
|
|
558
|
+
通过面向对象的方式管理配置,我们可以:
|
|
559
|
+
|
|
560
|
+
- 提高代码的可维护性和可测试性
|
|
561
|
+
- 确保配置的类型安全和完整性
|
|
562
|
+
- 方便地进行配置验证和调试
|
|
563
|
+
- 更好地管理配置依赖关系
|