P2 阶段交付物: - services/iam: 完整身份认证服务(users/roles/permissions/refresh_tokens 6 表 schema) - register/login/refresh/getUserInfo 4 个核心 API - bcrypt 密码哈希 + JWT 双 Token(access + refresh) - 复用 classes 黄金模板(errors/observability/middleware 三件套) - services/teacher-bff: 教师聚合 BFF - Promise.allSettled 并行聚合 IAM + classes 数据 - /teacher/dashboard 单一聚合端点 - packages/shared-proto/proto/iam.proto: IamService 契约(Register/Login/RefreshToken/GetUserInfo) - api-gateway: 新增 IamServiceURL/TeacherBffURL 配置 + /iam/* + /teacher/* 路由
106 lines
2.8 KiB
TypeScript
106 lines
2.8 KiB
TypeScript
export type ErrorType =
|
|
| 'validation'
|
|
| 'not_found'
|
|
| 'permission_denied'
|
|
| 'unauthorized'
|
|
| 'conflict'
|
|
| 'business'
|
|
| 'database'
|
|
| 'internal';
|
|
|
|
export interface ErrorDetails {
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export abstract class ApplicationError extends Error {
|
|
abstract readonly type: ErrorType;
|
|
abstract readonly statusCode: number;
|
|
readonly code: string;
|
|
readonly details?: ErrorDetails;
|
|
// traceId 改为可写,以便 GlobalErrorFilter 注入请求级 traceId
|
|
traceId?: string;
|
|
|
|
constructor(message: string, code: string, details?: ErrorDetails) {
|
|
super(message);
|
|
this.name = this.constructor.name;
|
|
this.code = code;
|
|
this.details = details;
|
|
}
|
|
|
|
toJSON(): Record<string, unknown> {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
code: this.code,
|
|
message: this.message,
|
|
details: this.details,
|
|
traceId: this.traceId,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export class ValidationError extends ApplicationError {
|
|
readonly type = 'validation' as const;
|
|
readonly statusCode = 400;
|
|
constructor(message: string, details?: ErrorDetails) {
|
|
super(message, 'IAM_VALIDATION_ERROR', details);
|
|
}
|
|
}
|
|
|
|
export class NotFoundError extends ApplicationError {
|
|
readonly type = 'not_found' as const;
|
|
readonly statusCode = 404;
|
|
constructor(resource: string, id: string) {
|
|
super(`${resource} not found: ${id}`, 'IAM_NOT_FOUND', { resource, id });
|
|
}
|
|
}
|
|
|
|
export class PermissionDeniedError extends ApplicationError {
|
|
readonly type = 'permission_denied' as const;
|
|
readonly statusCode = 403;
|
|
constructor(permission: string) {
|
|
super(`Permission denied: ${permission}`, 'IAM_PERMISSION_DENIED', { permission });
|
|
}
|
|
}
|
|
|
|
export class UnauthorizedError extends ApplicationError {
|
|
readonly type = 'unauthorized' as const;
|
|
readonly statusCode = 401;
|
|
constructor(message: string, details?: ErrorDetails) {
|
|
super(message, 'IAM_UNAUTHORIZED', details);
|
|
}
|
|
}
|
|
|
|
export class ConflictError extends ApplicationError {
|
|
readonly type = 'conflict' as const;
|
|
readonly statusCode = 409;
|
|
constructor(message: string, details?: ErrorDetails) {
|
|
super(message, 'IAM_CONFLICT', details);
|
|
}
|
|
}
|
|
|
|
export class BusinessError extends ApplicationError {
|
|
readonly type = 'business' as const;
|
|
readonly statusCode = 422;
|
|
constructor(message: string, details?: ErrorDetails) {
|
|
super(message, 'IAM_BUSINESS_ERROR', details);
|
|
}
|
|
}
|
|
|
|
export class DatabaseError extends ApplicationError {
|
|
readonly type = 'database' as const;
|
|
readonly statusCode = 500;
|
|
constructor(message: string, details?: ErrorDetails) {
|
|
super(message, 'IAM_DATABASE_ERROR', details);
|
|
}
|
|
}
|
|
|
|
export class InternalError extends ApplicationError {
|
|
readonly type = 'internal' as const;
|
|
readonly statusCode = 500;
|
|
constructor(message: string, details?: ErrorDetails) {
|
|
super(message, 'IAM_INTERNAL_ERROR', details);
|
|
}
|
|
}
|