feat(a0): 完成工程骨架与契约基线
ci / 质量门禁 (push) Canceled after 0s

- 建立 pnpm workspace + Turborepo 单仓、ESLint 9/Prettier/commitlint/husky 门禁
- NestJS + Fastify API:统一错误体、trace_id、Zod 管道、/health 真实依赖探活、OpenAPI 3.1 契约
- Prisma 基线:outbox_events/idempotency_keys/system_configs 与首版迁移、幂等种子
- shared 契约包:错误码、权限点、ULID、游标分页、命名/时间工具
- ui 包与 Web 壳层:Semi + Remix 图标统一出口、Cloud Blue 视觉、登录/工作台/错误页
- worker 骨架、Playwright 冒烟、Gitea CI、OpenAPI 快照与 oasdiff 回退脚本
- 补充 A0 实施报告与本地开发说明
This commit is contained in:
Stev_Wang committed 2026-11-02 17:01:50 +08:00
1 parent 9c3ab9768a
commit 04f49690f4
155 files changed
+18402 -28

No files matched your search

@@ -0,0 +1,94 @@
import { ErrorCode, nowIso } from '@lingxi/shared';
import { Inject, Injectable } from '@nestjs/common';
import { AppException } from '../../../common/errors/app-exception';
import { APP_CONFIG } from '../../../config/config.module';
import type { AppConfig } from '../../../config/env';
import type { PrismaService } from '../../../infra/prisma/prisma.service';
import type { RedisService } from '../../../infra/redis/redis.service';
import { PRISMA } from '../../../infra/tokens';
import { REDIS } from '../../../infra/tokens';
import type { ComponentHealth, HealthReport } from './health.types';
const API_VERSION = '0.1.0';
const CHECK_TIMEOUT_MS = 2000;
function withTimeout<T>(task: Promise<T>, timeoutMs: number): Promise<T> {
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`检查超时(${String(timeoutMs)}ms)`));
}, timeoutMs);
void task.then(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error: unknown) => {
clearTimeout(timer);
reject(error instanceof Error ? error : new Error(String(error)));
},
);
});
}
/**
* 健康检查:真实检查数据库(SELECT 1)与 Redis(PING),不只看端口(技术架构 §4.1.1)。
* 任一依赖不可用时返回 503 + 统一错误体,details 里带完整检查快照。
*/
@Injectable()
export class HealthService {
constructor(
@Inject(PRISMA) private readonly prisma: PrismaService,
@Inject(REDIS) private readonly redis: RedisService,
@Inject(APP_CONFIG) private readonly config: AppConfig,
) {}
async check(): Promise<HealthReport> {
const [database, redis] = await Promise.all([this.checkDatabase(), this.checkRedis()]);
const report: HealthReport = {
status: database.status === 'up' && redis.status === 'up' ? 'ok' : 'degraded',
env: this.config.nodeEnv,
version: API_VERSION,
uptimeSeconds: Math.round(process.uptime()),
checks: { database, redis },
timestamp: nowIso(),
};
if (report.status === 'degraded') {
throw new AppException({
code: ErrorCode.DEPENDENCY_UNAVAILABLE,
message: '依赖服务不可用',
details: report.checks,
});
}
return report;
}
private async checkDatabase(): Promise<ComponentHealth> {
const startedAt = Date.now();
try {
await withTimeout(this.prisma.$queryRaw`SELECT 1`, CHECK_TIMEOUT_MS);
return { status: 'up', latencyMs: Date.now() - startedAt };
} catch (error) {
return {
status: 'down',
latencyMs: Date.now() - startedAt,
error: error instanceof Error ? error.message : '未知错误',
};
}
}
private async checkRedis(): Promise<ComponentHealth> {
const startedAt = Date.now();
try {
await withTimeout(this.redis.ping(), CHECK_TIMEOUT_MS);
return { status: 'up', latencyMs: Date.now() - startedAt };
} catch (error) {
return {
status: 'down',
latencyMs: Date.now() - startedAt,
error: error instanceof Error ? error.message : '未知错误',
};
}
}
}