create-hest-app 0.1.0
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 +211 -0
- package/dist/index.js +127 -0
- package/package.json +50 -0
- package/templates/base/.prettierrc +33 -0
- package/templates/base/.vscode/extensions.json +79 -0
- package/templates/base/.vscode/settings.json +70 -0
- package/templates/base/README.md +562 -0
- package/templates/base/eslint.config.ts +26 -0
- package/templates/base/package.json +62 -0
- package/templates/base/src/app.controller.ts +39 -0
- package/templates/base/src/app.module.ts +12 -0
- package/templates/base/src/app.service.ts +30 -0
- package/templates/base/src/common/filters/http-exception.filter.ts +34 -0
- package/templates/base/src/common/interceptors/response.interceptor.ts +38 -0
- package/templates/base/src/index.ts +35 -0
- package/templates/base/src/modules/custom-validation/custom-validation.controller.ts +146 -0
- package/templates/base/src/modules/custom-validation/custom-validation.module.ts +10 -0
- package/templates/base/src/modules/custom-validation/custom-validation.service.ts +33 -0
- package/templates/base/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
- package/templates/base/src/modules/users/dto/user.dto.ts +64 -0
- package/templates/base/src/modules/users/entities/user.entity.ts +9 -0
- package/templates/base/src/modules/users/users.controller.ts +68 -0
- package/templates/base/src/modules/users/users.module.ts +10 -0
- package/templates/base/src/modules/users/users.service.ts +55 -0
- package/templates/base/tsconfig.json +19 -0
- package/templates/base_scalar/.prettierrc +32 -0
- package/templates/base_scalar/.vscode/extensions.json +79 -0
- package/templates/base_scalar/.vscode/settings.json +70 -0
- package/templates/base_scalar/README.md +562 -0
- package/templates/base_scalar/eslint.config.ts +26 -0
- package/templates/base_scalar/package.json +63 -0
- package/templates/base_scalar/src/app.controller.ts +196 -0
- package/templates/base_scalar/src/app.module.ts +12 -0
- package/templates/base_scalar/src/app.service.ts +30 -0
- package/templates/base_scalar/src/common/filters/http-exception.filter.ts +34 -0
- package/templates/base_scalar/src/common/interceptors/response.interceptor.ts +38 -0
- package/templates/base_scalar/src/index.ts +67 -0
- package/templates/base_scalar/src/modules/custom-validation/custom-validation.controller.ts +146 -0
- package/templates/base_scalar/src/modules/custom-validation/custom-validation.module.ts +10 -0
- package/templates/base_scalar/src/modules/custom-validation/custom-validation.service.ts +33 -0
- package/templates/base_scalar/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
- package/templates/base_scalar/src/modules/users/dto/user.dto.ts +64 -0
- package/templates/base_scalar/src/modules/users/entities/user.entity.ts +9 -0
- package/templates/base_scalar/src/modules/users/users.controller.ts +68 -0
- package/templates/base_scalar/src/modules/users/users.module.ts +10 -0
- package/templates/base_scalar/src/modules/users/users.service.ts +55 -0
- package/templates/base_scalar/tsconfig.json +19 -0
- package/templates/cqrs/.prettierrc +33 -0
- package/templates/cqrs/.vscode/extensions.json +79 -0
- package/templates/cqrs/.vscode/settings.json +70 -0
- package/templates/cqrs/README.md +234 -0
- package/templates/cqrs/eslint.config.ts +26 -0
- package/templates/cqrs/package.json +62 -0
- package/templates/cqrs/src/app.controller.ts +28 -0
- package/templates/cqrs/src/app.module.ts +12 -0
- package/templates/cqrs/src/app.service.ts +8 -0
- package/templates/cqrs/src/common/filters/http-exception.filter.ts +34 -0
- package/templates/cqrs/src/common/interceptors/response.interceptor.ts +38 -0
- package/templates/cqrs/src/index.ts +38 -0
- package/templates/cqrs/src/modules/custom-validation/custom-validation.controller.ts +146 -0
- package/templates/cqrs/src/modules/custom-validation/custom-validation.module.ts +10 -0
- package/templates/cqrs/src/modules/custom-validation/custom-validation.service.ts +33 -0
- package/templates/cqrs/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
- package/templates/cqrs/src/modules/users/dto/user.dto.ts +64 -0
- package/templates/cqrs/src/modules/users/entities/user.entity.ts +9 -0
- package/templates/cqrs/src/modules/users/users.controller.ts +68 -0
- package/templates/cqrs/src/modules/users/users.module.ts +10 -0
- package/templates/cqrs/src/modules/users/users.service.ts +55 -0
- package/templates/cqrs/src/test-error-scenarios.ts +54 -0
- package/templates/cqrs/src/users/commands/create-user.command.ts +8 -0
- package/templates/cqrs/src/users/commands/index.ts +2 -0
- package/templates/cqrs/src/users/commands/update-user.command.ts +11 -0
- package/templates/cqrs/src/users/entities/index.ts +1 -0
- package/templates/cqrs/src/users/entities/user.entity.ts +22 -0
- package/templates/cqrs/src/users/events/index.ts +2 -0
- package/templates/cqrs/src/users/events/user-created.event.ts +8 -0
- package/templates/cqrs/src/users/events/user-updated.event.ts +8 -0
- package/templates/cqrs/src/users/handlers/create-user.handler.ts +26 -0
- package/templates/cqrs/src/users/handlers/get-all-users.handler.ts +15 -0
- package/templates/cqrs/src/users/handlers/get-user.handler.ts +15 -0
- package/templates/cqrs/src/users/handlers/index.ts +6 -0
- package/templates/cqrs/src/users/handlers/update-user.handler.ts +33 -0
- package/templates/cqrs/src/users/handlers/user-created.handler.ts +15 -0
- package/templates/cqrs/src/users/handlers/user-updated.handler.ts +15 -0
- package/templates/cqrs/src/users/index.ts +8 -0
- package/templates/cqrs/src/users/queries/get-all-users.query.ts +8 -0
- package/templates/cqrs/src/users/queries/get-user.query.ts +12 -0
- package/templates/cqrs/src/users/queries/index.ts +2 -0
- package/templates/cqrs/src/users/repositories/index.ts +1 -0
- package/templates/cqrs/src/users/repositories/user.repository.ts +51 -0
- package/templates/cqrs/src/users/user.controller.ts +66 -0
- package/templates/cqrs/src/users/user.module.ts +30 -0
- package/templates/cqrs/tsconfig.json +19 -0
- package/templates/cqrs_scalar/.prettierrc +33 -0
- package/templates/cqrs_scalar/.vscode/extensions.json +79 -0
- package/templates/cqrs_scalar/.vscode/settings.json +70 -0
- package/templates/cqrs_scalar/README.md +234 -0
- package/templates/cqrs_scalar/eslint.config.ts +26 -0
- package/templates/cqrs_scalar/package.json +62 -0
- package/templates/cqrs_scalar/src/app.controller.ts +28 -0
- package/templates/cqrs_scalar/src/app.module.ts +12 -0
- package/templates/cqrs_scalar/src/app.service.ts +8 -0
- package/templates/cqrs_scalar/src/common/filters/http-exception.filter.ts +34 -0
- package/templates/cqrs_scalar/src/common/interceptors/response.interceptor.ts +38 -0
- package/templates/cqrs_scalar/src/index.ts +38 -0
- package/templates/cqrs_scalar/src/modules/custom-validation/custom-validation.controller.ts +146 -0
- package/templates/cqrs_scalar/src/modules/custom-validation/custom-validation.module.ts +10 -0
- package/templates/cqrs_scalar/src/modules/custom-validation/custom-validation.service.ts +33 -0
- package/templates/cqrs_scalar/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
- package/templates/cqrs_scalar/src/modules/users/dto/user.dto.ts +64 -0
- package/templates/cqrs_scalar/src/modules/users/entities/user.entity.ts +9 -0
- package/templates/cqrs_scalar/src/modules/users/users.controller.ts +68 -0
- package/templates/cqrs_scalar/src/modules/users/users.module.ts +10 -0
- package/templates/cqrs_scalar/src/modules/users/users.service.ts +55 -0
- package/templates/cqrs_scalar/src/test-error-scenarios.ts +54 -0
- package/templates/cqrs_scalar/src/users/commands/create-user.command.ts +8 -0
- package/templates/cqrs_scalar/src/users/commands/index.ts +2 -0
- package/templates/cqrs_scalar/src/users/commands/update-user.command.ts +11 -0
- package/templates/cqrs_scalar/src/users/entities/index.ts +1 -0
- package/templates/cqrs_scalar/src/users/entities/user.entity.ts +22 -0
- package/templates/cqrs_scalar/src/users/events/index.ts +2 -0
- package/templates/cqrs_scalar/src/users/events/user-created.event.ts +8 -0
- package/templates/cqrs_scalar/src/users/events/user-updated.event.ts +8 -0
- package/templates/cqrs_scalar/src/users/handlers/create-user.handler.ts +26 -0
- package/templates/cqrs_scalar/src/users/handlers/get-all-users.handler.ts +15 -0
- package/templates/cqrs_scalar/src/users/handlers/get-user.handler.ts +15 -0
- package/templates/cqrs_scalar/src/users/handlers/index.ts +6 -0
- package/templates/cqrs_scalar/src/users/handlers/update-user.handler.ts +33 -0
- package/templates/cqrs_scalar/src/users/handlers/user-created.handler.ts +15 -0
- package/templates/cqrs_scalar/src/users/handlers/user-updated.handler.ts +15 -0
- package/templates/cqrs_scalar/src/users/index.ts +8 -0
- package/templates/cqrs_scalar/src/users/queries/get-all-users.query.ts +8 -0
- package/templates/cqrs_scalar/src/users/queries/get-user.query.ts +12 -0
- package/templates/cqrs_scalar/src/users/queries/index.ts +2 -0
- package/templates/cqrs_scalar/src/users/repositories/index.ts +1 -0
- package/templates/cqrs_scalar/src/users/repositories/user.repository.ts +51 -0
- package/templates/cqrs_scalar/src/users/user.controller.ts +66 -0
- package/templates/cqrs_scalar/src/users/user.module.ts +30 -0
- package/templates/cqrs_scalar/tsconfig.json +19 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
import { Controller, Get, NotFoundException, Param, Post } from '@hestjs/core';
|
2
|
+
import {
|
3
|
+
ApiBody,
|
4
|
+
ApiOperation,
|
5
|
+
ApiParam,
|
6
|
+
ApiResponse,
|
7
|
+
ApiTags,
|
8
|
+
} from '@hestjs/scalar';
|
9
|
+
import { Body } from '@hestjs/validation';
|
10
|
+
import { AppService } from './app.service';
|
11
|
+
import { CreateUserDto } from './modules/users/dto/user.dto';
|
12
|
+
|
13
|
+
@Controller('/api')
|
14
|
+
@ApiTags('Application')
|
15
|
+
export class AppController {
|
16
|
+
constructor(private readonly appService: AppService) {}
|
17
|
+
|
18
|
+
@Get('/')
|
19
|
+
@ApiOperation({
|
20
|
+
summary: 'Get hello message',
|
21
|
+
description: 'Returns a hello world message',
|
22
|
+
tags: ['Health Check', 'Application'],
|
23
|
+
})
|
24
|
+
@ApiResponse('200', {
|
25
|
+
description: 'Successful response',
|
26
|
+
content: {
|
27
|
+
'application/json': {
|
28
|
+
schema: {
|
29
|
+
type: 'object',
|
30
|
+
properties: {
|
31
|
+
message: { type: 'string', example: 'Hello World!' },
|
32
|
+
},
|
33
|
+
},
|
34
|
+
},
|
35
|
+
},
|
36
|
+
})
|
37
|
+
getHello() {
|
38
|
+
return { message: this.appService.getHello() };
|
39
|
+
}
|
40
|
+
|
41
|
+
@Get('/users')
|
42
|
+
@ApiOperation({
|
43
|
+
summary: 'Get all users',
|
44
|
+
description: 'Returns a list of all users',
|
45
|
+
})
|
46
|
+
@ApiResponse('200', {
|
47
|
+
description: 'List of users',
|
48
|
+
content: {
|
49
|
+
'application/json': {
|
50
|
+
schema: {
|
51
|
+
type: 'array',
|
52
|
+
items: {
|
53
|
+
type: 'object',
|
54
|
+
properties: {
|
55
|
+
id: { type: 'string' },
|
56
|
+
name: { type: 'string' },
|
57
|
+
email: { type: 'string' },
|
58
|
+
},
|
59
|
+
},
|
60
|
+
},
|
61
|
+
},
|
62
|
+
},
|
63
|
+
})
|
64
|
+
getUsers() {
|
65
|
+
return this.appService.getUsers();
|
66
|
+
}
|
67
|
+
|
68
|
+
@Get('/users/:id')
|
69
|
+
@ApiOperation({
|
70
|
+
summary: 'Get user by ID',
|
71
|
+
description: 'Returns a single user by their ID',
|
72
|
+
})
|
73
|
+
@ApiParam('id', {
|
74
|
+
description: 'User ID',
|
75
|
+
schema: { type: 'string' },
|
76
|
+
example: '123',
|
77
|
+
})
|
78
|
+
@ApiResponse('200', {
|
79
|
+
description: 'User found',
|
80
|
+
content: {
|
81
|
+
'application/json': {
|
82
|
+
schema: {
|
83
|
+
type: 'object',
|
84
|
+
properties: {
|
85
|
+
id: { type: 'string' },
|
86
|
+
name: { type: 'string' },
|
87
|
+
email: { type: 'string' },
|
88
|
+
},
|
89
|
+
},
|
90
|
+
},
|
91
|
+
},
|
92
|
+
})
|
93
|
+
@ApiResponse('404', {
|
94
|
+
description: 'User not found',
|
95
|
+
content: {
|
96
|
+
'application/json': {
|
97
|
+
schema: {
|
98
|
+
type: 'object',
|
99
|
+
properties: {
|
100
|
+
message: { type: 'string', example: 'User with id 123 not found' },
|
101
|
+
},
|
102
|
+
},
|
103
|
+
},
|
104
|
+
},
|
105
|
+
})
|
106
|
+
getUser(@Param('id') id: string) {
|
107
|
+
const user = this.appService.getUser(id);
|
108
|
+
if (!user) {
|
109
|
+
throw new NotFoundException(`User with id ${id} not found`);
|
110
|
+
}
|
111
|
+
return user;
|
112
|
+
}
|
113
|
+
|
114
|
+
@Post('/users')
|
115
|
+
@ApiOperation({
|
116
|
+
summary: 'Create a new user',
|
117
|
+
description: 'Creates a new user with the provided data',
|
118
|
+
})
|
119
|
+
@ApiBody(
|
120
|
+
{
|
121
|
+
'application/json': {
|
122
|
+
schema: {
|
123
|
+
type: 'object',
|
124
|
+
required: ['name', 'email'],
|
125
|
+
properties: {
|
126
|
+
name: { type: 'string', example: 'John Doe' },
|
127
|
+
email: {
|
128
|
+
type: 'string',
|
129
|
+
format: 'email',
|
130
|
+
example: 'john@example.com',
|
131
|
+
},
|
132
|
+
},
|
133
|
+
},
|
134
|
+
},
|
135
|
+
},
|
136
|
+
{
|
137
|
+
description: 'User creation data',
|
138
|
+
required: true,
|
139
|
+
},
|
140
|
+
)
|
141
|
+
@ApiResponse('201', {
|
142
|
+
description: 'User created successfully',
|
143
|
+
content: {
|
144
|
+
'application/json': {
|
145
|
+
schema: {
|
146
|
+
type: 'object',
|
147
|
+
properties: {
|
148
|
+
id: { type: 'string' },
|
149
|
+
name: { type: 'string' },
|
150
|
+
email: { type: 'string' },
|
151
|
+
createdAt: { type: 'string', format: 'date-time' },
|
152
|
+
},
|
153
|
+
},
|
154
|
+
},
|
155
|
+
},
|
156
|
+
})
|
157
|
+
@ApiResponse('400', {
|
158
|
+
description: 'Invalid input data',
|
159
|
+
content: {
|
160
|
+
'application/json': {
|
161
|
+
schema: {
|
162
|
+
type: 'object',
|
163
|
+
properties: {
|
164
|
+
message: { type: 'string' },
|
165
|
+
errors: { type: 'array', items: { type: 'string' } },
|
166
|
+
},
|
167
|
+
},
|
168
|
+
},
|
169
|
+
},
|
170
|
+
})
|
171
|
+
createUser(@Body(CreateUserDto) createUserDto: CreateUserDto) {
|
172
|
+
return this.appService.createUser(createUserDto);
|
173
|
+
}
|
174
|
+
|
175
|
+
@Get('/error')
|
176
|
+
@ApiOperation({
|
177
|
+
summary: 'Test error endpoint',
|
178
|
+
description: 'Throws an error for testing error handling',
|
179
|
+
})
|
180
|
+
@ApiResponse('500', {
|
181
|
+
description: 'Internal server error',
|
182
|
+
content: {
|
183
|
+
'application/json': {
|
184
|
+
schema: {
|
185
|
+
type: 'object',
|
186
|
+
properties: {
|
187
|
+
message: { type: 'string', example: 'This is a test error' },
|
188
|
+
},
|
189
|
+
},
|
190
|
+
},
|
191
|
+
},
|
192
|
+
})
|
193
|
+
throwError() {
|
194
|
+
throw new Error('This is a test error');
|
195
|
+
}
|
196
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Module } from '@hestjs/core';
|
2
|
+
import { AppController } from './app.controller';
|
3
|
+
import { AppService } from './app.service';
|
4
|
+
import { UsersModule } from './modules/users/users.module';
|
5
|
+
import { CustomValidationModule } from './modules/custom-validation/custom-validation.module';
|
6
|
+
|
7
|
+
@Module({
|
8
|
+
imports: [UsersModule, CustomValidationModule],
|
9
|
+
controllers: [AppController],
|
10
|
+
providers: [AppService],
|
11
|
+
})
|
12
|
+
export class AppModule {}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { Injectable } from '@hestjs/core';
|
2
|
+
|
3
|
+
@Injectable()
|
4
|
+
export class AppService {
|
5
|
+
private users = [
|
6
|
+
{ id: '1', name: 'John Doe', email: 'john@example.com' },
|
7
|
+
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' },
|
8
|
+
];
|
9
|
+
|
10
|
+
getHello(): string {
|
11
|
+
return 'Hello from HestJS!';
|
12
|
+
}
|
13
|
+
|
14
|
+
getUsers() {
|
15
|
+
return this.users;
|
16
|
+
}
|
17
|
+
|
18
|
+
getUser(id: string) {
|
19
|
+
return this.users.find(user => user.id === id);
|
20
|
+
}
|
21
|
+
|
22
|
+
createUser(userData: { name: string; email: string }) {
|
23
|
+
const user = {
|
24
|
+
id: Date.now().toString(),
|
25
|
+
...userData,
|
26
|
+
};
|
27
|
+
this.users.push(user);
|
28
|
+
return user;
|
29
|
+
}
|
30
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import type {
|
2
|
+
ArgumentsHost,
|
3
|
+
ExceptionFilter,
|
4
|
+
HttpException,
|
5
|
+
} from '@hestjs/core';
|
6
|
+
import { createLogger } from '@hestjs/core';
|
7
|
+
|
8
|
+
const logger = createLogger('HttpExceptionFilter');
|
9
|
+
|
10
|
+
/**
|
11
|
+
* 自定义 HTTP 异常过滤器
|
12
|
+
*/
|
13
|
+
export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
|
14
|
+
catch(exception: HttpException, host: ArgumentsHost) {
|
15
|
+
const ctx = host.getContext();
|
16
|
+
const request = host.getRequest();
|
17
|
+
|
18
|
+
const status = exception.status;
|
19
|
+
const response = {
|
20
|
+
statusCode: status,
|
21
|
+
timestamp: new Date().toISOString(),
|
22
|
+
path: request.url,
|
23
|
+
message: exception.message,
|
24
|
+
error: exception.error || 'Http Exception',
|
25
|
+
};
|
26
|
+
|
27
|
+
logger.error(`🔥 HTTP Exception [${status}]: ${exception.message}`, {
|
28
|
+
requestUrl: request.url,
|
29
|
+
stack: exception.stack,
|
30
|
+
});
|
31
|
+
|
32
|
+
return ctx.json(response, status);
|
33
|
+
}
|
34
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import type { CallHandler, ExecutionContext, Interceptor } from '@hestjs/core';
|
2
|
+
import { createLogger } from '@hestjs/core';
|
3
|
+
|
4
|
+
const logger = createLogger('ResponseInterceptor');
|
5
|
+
|
6
|
+
/**
|
7
|
+
* 响应转换拦截器
|
8
|
+
*/
|
9
|
+
export class ResponseInterceptor implements Interceptor {
|
10
|
+
async intercept(
|
11
|
+
context: ExecutionContext,
|
12
|
+
next: CallHandler,
|
13
|
+
): Promise<{
|
14
|
+
success: boolean;
|
15
|
+
data: unknown;
|
16
|
+
timestamp: string;
|
17
|
+
duration: string;
|
18
|
+
}> {
|
19
|
+
const startTime = Date.now();
|
20
|
+
const request = context.switchToHttp().getRequest();
|
21
|
+
|
22
|
+
logger.info(`🚀 Request: ${request.method} ${request.url}`);
|
23
|
+
|
24
|
+
const result = await next.handle();
|
25
|
+
|
26
|
+
const duration = Date.now() - startTime;
|
27
|
+
logger.info(
|
28
|
+
`✅ Response: ${request.method} ${request.url} - ${duration}ms`,
|
29
|
+
);
|
30
|
+
|
31
|
+
return {
|
32
|
+
success: true,
|
33
|
+
data: result,
|
34
|
+
timestamp: new Date().toISOString(),
|
35
|
+
duration: `${duration}ms`,
|
36
|
+
};
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import { HestFactory } from '@hestjs/core';
|
2
|
+
import { logger } from '@hestjs/logger';
|
3
|
+
import '@hestjs/scalar'; // 导入scalar扩展
|
4
|
+
import { ValidationInterceptor } from '@hestjs/validation';
|
5
|
+
import { cors } from 'hono/cors';
|
6
|
+
import { AppController } from './app.controller';
|
7
|
+
import { AppModule } from './app.module';
|
8
|
+
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
|
9
|
+
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
|
10
|
+
|
11
|
+
async function bootstrap() {
|
12
|
+
try {
|
13
|
+
logger.info('🚀 Starting HestJS application...');
|
14
|
+
|
15
|
+
const app = await HestFactory.create(AppModule);
|
16
|
+
app.hono().use(cors()); // 使用 Hono 的 CORS 中间件
|
17
|
+
// app.hono().use('*', log()); // 使用 Hono 的日志中间件
|
18
|
+
|
19
|
+
// 全局拦截器 - 验证拦截器应该在响应拦截器之前
|
20
|
+
app.useGlobalInterceptors(new ValidationInterceptor());
|
21
|
+
app.useGlobalInterceptors(new ResponseInterceptor());
|
22
|
+
|
23
|
+
// 全局异常过滤器
|
24
|
+
app.useGlobalFilters(new HttpExceptionFilter());
|
25
|
+
|
26
|
+
// 设置OpenAPI规范端点 - 基于注解生成
|
27
|
+
app.useScalarWithControllers(
|
28
|
+
[AppController], // 传入需要生成文档的控制器
|
29
|
+
{
|
30
|
+
info: {
|
31
|
+
title: 'HestJS Demo API',
|
32
|
+
version: '1.0.0',
|
33
|
+
description:
|
34
|
+
'A demonstration of HestJS framework capabilities with Scalar API documentation',
|
35
|
+
},
|
36
|
+
servers: [
|
37
|
+
{
|
38
|
+
url: 'http://localhost:3002',
|
39
|
+
description: 'Development server',
|
40
|
+
},
|
41
|
+
],
|
42
|
+
},
|
43
|
+
{
|
44
|
+
path: '/docs',
|
45
|
+
theme: 'elysia', // 使用elysia主题
|
46
|
+
enableMarkdown: true,
|
47
|
+
markdownPath: '/api-docs.md',
|
48
|
+
},
|
49
|
+
);
|
50
|
+
|
51
|
+
logger.info('📚 API Documentation available at:');
|
52
|
+
logger.info(' • Scalar UI: http://localhost:3002/docs');
|
53
|
+
logger.info(' • OpenAPI JSON: http://localhost:3002/openapi.json');
|
54
|
+
logger.info(' • Markdown (for LLMs): http://localhost:3002/api-docs.md');
|
55
|
+
|
56
|
+
Bun.serve({
|
57
|
+
port: 3002,
|
58
|
+
fetch: app.hono().fetch,
|
59
|
+
reusePort: true, // 启用端口复用
|
60
|
+
});
|
61
|
+
} catch (error) {
|
62
|
+
logger.error('❌ Failed to start application:', error);
|
63
|
+
process.exit(1);
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
bootstrap();
|
@@ -0,0 +1,146 @@
|
|
1
|
+
import { Controller, Get, Post } from '@hestjs/core';
|
2
|
+
import { Body } from '@hestjs/validation';
|
3
|
+
import { CustomValidationDto, SearchQueryDto } from './dto/custom-validation.dto';
|
4
|
+
import { CustomValidationService } from './custom-validation.service';
|
5
|
+
|
6
|
+
/**
|
7
|
+
* 自定义验证控制器 - 展示 TypeBox 自定义验证功能
|
8
|
+
*/
|
9
|
+
@Controller('/api/custom')
|
10
|
+
export class CustomValidationController {
|
11
|
+
constructor(
|
12
|
+
private readonly customValidationService: CustomValidationService,
|
13
|
+
) {}
|
14
|
+
|
15
|
+
@Get('/')
|
16
|
+
async getInfo() {
|
17
|
+
return {
|
18
|
+
success: true,
|
19
|
+
message: 'HestJS 自定义验证功能示例',
|
20
|
+
features: [
|
21
|
+
'🔧 TypeBox 自定义 Schema 验证',
|
22
|
+
'🏗️ SchemaFactory 便捷构建器',
|
23
|
+
'📦 CommonValidators 常用验证',
|
24
|
+
'🔗 联合类型验证',
|
25
|
+
'📐 嵌套对象验证',
|
26
|
+
'📱 中国手机号验证',
|
27
|
+
'🔗 UUID 验证',
|
28
|
+
'📍 地理坐标验证',
|
29
|
+
],
|
30
|
+
endpoints: {
|
31
|
+
'POST /api/custom/validate': '测试自定义验证',
|
32
|
+
'POST /api/custom/search': '测试搜索参数验证',
|
33
|
+
'GET /api/custom/examples': '获取验证示例',
|
34
|
+
},
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
38
|
+
@Post('/validate')
|
39
|
+
async validateCustomData(
|
40
|
+
@Body(CustomValidationDto) data: CustomValidationDto,
|
41
|
+
) {
|
42
|
+
const result = this.customValidationService.processCustomData(data);
|
43
|
+
return {
|
44
|
+
success: true,
|
45
|
+
message: '自定义验证通过!',
|
46
|
+
result,
|
47
|
+
validationInfo: {
|
48
|
+
username: `用户名 "${data.username}" 通过正则验证`,
|
49
|
+
role: `角色 "${data.role}" 通过联合类型验证`,
|
50
|
+
userId: `UUID "${data.userId}" 格式验证通过`,
|
51
|
+
phoneNumber: data.phoneNumber
|
52
|
+
? `手机号 "${data.phoneNumber}" 通过中国手机号验证`
|
53
|
+
: '未提供手机号',
|
54
|
+
location: data.location
|
55
|
+
? `坐标 (${data.location.lat}, ${data.location.lng}) 验证通过`
|
56
|
+
: '未提供坐标',
|
57
|
+
emails: data.emails
|
58
|
+
? `邮箱列表包含 ${data.emails.length} 个地址`
|
59
|
+
: '未提供邮箱',
|
60
|
+
},
|
61
|
+
};
|
62
|
+
}
|
63
|
+
|
64
|
+
@Post('/search')
|
65
|
+
async searchWithValidation(@Body(SearchQueryDto) query: SearchQueryDto) {
|
66
|
+
const result = this.customValidationService.processSearch(query);
|
67
|
+
return {
|
68
|
+
success: true,
|
69
|
+
message: '搜索参数验证通过!',
|
70
|
+
result,
|
71
|
+
validationInfo: {
|
72
|
+
query: query.q || '无搜索关键词',
|
73
|
+
pagination: query.pagination || '使用默认分页',
|
74
|
+
},
|
75
|
+
};
|
76
|
+
}
|
77
|
+
|
78
|
+
@Get('/examples')
|
79
|
+
async getValidationExamples() {
|
80
|
+
return {
|
81
|
+
success: true,
|
82
|
+
message: '自定义验证示例',
|
83
|
+
examples: {
|
84
|
+
customValidation: {
|
85
|
+
description: 'POST /api/custom/validate',
|
86
|
+
validExample: {
|
87
|
+
username: 'john_doe123',
|
88
|
+
role: 'user',
|
89
|
+
userId: '123e4567-e89b-12d3-a456-426614174000',
|
90
|
+
phoneNumber: '13812345678',
|
91
|
+
location: { lat: 39.9042, lng: 116.4074 },
|
92
|
+
emails: ['john@example.com', 'john.doe@company.com'],
|
93
|
+
},
|
94
|
+
invalidExample: {
|
95
|
+
username: 'a', // 太短
|
96
|
+
role: 'invalid_role', // 不在联合类型中
|
97
|
+
userId: 'not-a-uuid', // 无效 UUID
|
98
|
+
phoneNumber: '123456', // 无效手机号
|
99
|
+
location: { lat: 200, lng: 200 }, // 超出范围
|
100
|
+
emails: ['invalid-email', 'another@invalid'], // 无效邮箱
|
101
|
+
},
|
102
|
+
},
|
103
|
+
searchValidation: {
|
104
|
+
description: 'POST /api/custom/search',
|
105
|
+
validExample: {
|
106
|
+
q: 'TypeScript',
|
107
|
+
pagination: {
|
108
|
+
page: 1,
|
109
|
+
limit: 10,
|
110
|
+
sort: 'createdAt',
|
111
|
+
order: 'desc',
|
112
|
+
},
|
113
|
+
},
|
114
|
+
invalidExample: {
|
115
|
+
q: 123, // 应该是字符串
|
116
|
+
pagination: {
|
117
|
+
page: 0, // 应该 >= 1
|
118
|
+
limit: 1000, // 应该 <= 100
|
119
|
+
order: 'invalid', // 应该是 'asc' 或 'desc'
|
120
|
+
},
|
121
|
+
},
|
122
|
+
},
|
123
|
+
},
|
124
|
+
typeboxFeatures: {
|
125
|
+
basicTypes: [
|
126
|
+
'Type.String()',
|
127
|
+
'Type.Number()',
|
128
|
+
'Type.Boolean()',
|
129
|
+
'Type.Array()',
|
130
|
+
],
|
131
|
+
advancedTypes: [
|
132
|
+
'Type.Union()',
|
133
|
+
'Type.Object()',
|
134
|
+
'Type.Intersect()',
|
135
|
+
'Type.Optional()',
|
136
|
+
],
|
137
|
+
formats: ['email', 'date', 'date-time', 'uri', 'uuid'],
|
138
|
+
patterns: ['正则表达式验证', '长度限制', '数值范围', '枚举值'],
|
139
|
+
customValidators: [
|
140
|
+
'SchemaFactory 便捷方法',
|
141
|
+
'CommonValidators 常用验证',
|
142
|
+
],
|
143
|
+
},
|
144
|
+
};
|
145
|
+
}
|
146
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { Module } from '@hestjs/core';
|
2
|
+
import { CustomValidationController } from './custom-validation.controller';
|
3
|
+
import { CustomValidationService } from './custom-validation.service';
|
4
|
+
|
5
|
+
@Module({
|
6
|
+
controllers: [CustomValidationController],
|
7
|
+
providers: [CustomValidationService],
|
8
|
+
exports: [CustomValidationService],
|
9
|
+
})
|
10
|
+
export class CustomValidationModule {}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { Injectable } from '@hestjs/core';
|
2
|
+
import type { CustomValidationDto, SearchQueryDto } from './dto/custom-validation.dto';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* 自定义验证服务
|
6
|
+
*/
|
7
|
+
@Injectable()
|
8
|
+
export class CustomValidationService {
|
9
|
+
processCustomData(data: CustomValidationDto) {
|
10
|
+
// 模拟处理逻辑
|
11
|
+
return {
|
12
|
+
processed: true,
|
13
|
+
data: {
|
14
|
+
...data,
|
15
|
+
processedAt: new Date().toISOString(),
|
16
|
+
validation: 'TypeBox 自定义验证通过',
|
17
|
+
},
|
18
|
+
};
|
19
|
+
}
|
20
|
+
|
21
|
+
processSearch(query: SearchQueryDto) {
|
22
|
+
// 模拟搜索逻辑
|
23
|
+
return {
|
24
|
+
results: [
|
25
|
+
{ id: 1, title: '搜索结果 1', relevance: 0.95 },
|
26
|
+
{ id: 2, title: '搜索结果 2', relevance: 0.87 },
|
27
|
+
],
|
28
|
+
query,
|
29
|
+
total: 2,
|
30
|
+
searchedAt: new Date().toISOString(),
|
31
|
+
};
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import {
|
2
|
+
CommonValidators,
|
3
|
+
Custom,
|
4
|
+
IsOptional,
|
5
|
+
IsString,
|
6
|
+
SchemaFactory,
|
7
|
+
} from '@hestjs/validation';
|
8
|
+
import { Type } from '@sinclair/typebox';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* 展示自定义验证功能的简化 DTO
|
12
|
+
*/
|
13
|
+
export class CustomValidationDto {
|
14
|
+
// 使用基础的 TypeBox API
|
15
|
+
@Custom(
|
16
|
+
Type.String({ minLength: 3, maxLength: 20, pattern: '^[a-zA-Z0-9_]+$' }),
|
17
|
+
{
|
18
|
+
message: '用户名必须是3-20位字母、数字或下划线',
|
19
|
+
},
|
20
|
+
)
|
21
|
+
username!: string;
|
22
|
+
|
23
|
+
// 使用联合类型
|
24
|
+
@Custom(
|
25
|
+
Type.Union([
|
26
|
+
Type.Literal('admin'),
|
27
|
+
Type.Literal('user'),
|
28
|
+
Type.Literal('guest'),
|
29
|
+
]),
|
30
|
+
{
|
31
|
+
message: '角色必须是 admin、user 或 guest',
|
32
|
+
},
|
33
|
+
)
|
34
|
+
role!: 'admin' | 'user' | 'guest';
|
35
|
+
|
36
|
+
// 使用 CommonValidators 的便捷方法
|
37
|
+
@CommonValidators.UUID({ message: '必须是有效的 UUID' })
|
38
|
+
userId!: string;
|
39
|
+
|
40
|
+
// 使用 SchemaFactory 的便捷方法
|
41
|
+
@Custom(SchemaFactory.chinesePhoneNumber(), {
|
42
|
+
message: '必须是有效的中国手机号',
|
43
|
+
optional: true,
|
44
|
+
})
|
45
|
+
phoneNumber?: string;
|
46
|
+
|
47
|
+
// 自定义对象验证
|
48
|
+
@Custom(
|
49
|
+
Type.Object({
|
50
|
+
lat: Type.Number({ minimum: -90, maximum: 90 }),
|
51
|
+
lng: Type.Number({ minimum: -180, maximum: 180 }),
|
52
|
+
}),
|
53
|
+
{ optional: true, message: '坐标必须在有效范围内' },
|
54
|
+
)
|
55
|
+
location?: {
|
56
|
+
lat: number;
|
57
|
+
lng: number;
|
58
|
+
};
|
59
|
+
|
60
|
+
// 数组验证
|
61
|
+
@Custom(Type.Array(Type.String({ format: 'email' })), {
|
62
|
+
optional: true,
|
63
|
+
message: '邮箱列表必须是有效的邮箱地址数组',
|
64
|
+
})
|
65
|
+
emails?: string[];
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* 搜索查询 DTO - 展示复杂对象验证
|
70
|
+
*/
|
71
|
+
export class SearchQueryDto {
|
72
|
+
@IsOptional()
|
73
|
+
@IsString({ message: '搜索关键词必须是字符串' })
|
74
|
+
q?: string;
|
75
|
+
|
76
|
+
// 使用 SchemaFactory 构建复杂的分页对象
|
77
|
+
@Custom(
|
78
|
+
Type.Object({
|
79
|
+
page: Type.Number({ minimum: 1 }),
|
80
|
+
limit: Type.Number({ minimum: 1, maximum: 100 }),
|
81
|
+
sort: Type.Optional(Type.String()),
|
82
|
+
order: Type.Optional(
|
83
|
+
Type.Union([Type.Literal('asc'), Type.Literal('desc')]),
|
84
|
+
),
|
85
|
+
}),
|
86
|
+
{
|
87
|
+
optional: true,
|
88
|
+
message: '分页参数格式错误',
|
89
|
+
},
|
90
|
+
)
|
91
|
+
pagination?: {
|
92
|
+
page: number;
|
93
|
+
limit: number;
|
94
|
+
sort?: string;
|
95
|
+
order?: 'asc' | 'desc';
|
96
|
+
};
|
97
|
+
|
98
|
+
// 使用联合类型进行复杂验证
|
99
|
+
@Custom(
|
100
|
+
Type.Union([
|
101
|
+
Type.Array(Type.String()),
|
102
|
+
Type.String(),
|
103
|
+
Type.Null(),
|
104
|
+
Type.Undefined(),
|
105
|
+
]),
|
106
|
+
{
|
107
|
+
optional: true,
|
108
|
+
message: '标签可以是字符串、字符串数组或空值',
|
109
|
+
},
|
110
|
+
)
|
111
|
+
tags?: string | string[] | null;
|
112
|
+
|
113
|
+
// 使用数值范围验证
|
114
|
+
@Custom(Type.Number({ minimum: 0, maximum: 100 }), {
|
115
|
+
optional: true,
|
116
|
+
message: '评分必须在 0-100 之间',
|
117
|
+
})
|
118
|
+
score?: number;
|
119
|
+
|
120
|
+
// 使用日期字符串验证
|
121
|
+
@Custom(Type.String({ format: 'date' }), {
|
122
|
+
optional: true,
|
123
|
+
message: '开始日期格式必须是 YYYY-MM-DD',
|
124
|
+
})
|
125
|
+
startDate?: string;
|
126
|
+
|
127
|
+
@Custom(Type.String({ format: 'date' }), {
|
128
|
+
optional: true,
|
129
|
+
message: '结束日期格式必须是 YYYY-MM-DD',
|
130
|
+
})
|
131
|
+
endDate?: string;
|
132
|
+
}
|