From 5860f613c8dee1bf3e8d65c6b04de4a106aa11b9 Mon Sep 17 00:00:00 2001 From: FOV-RGT Date: Mon, 16 Feb 2026 12:02:40 +0800 Subject: [PATCH 1/3] refactor: improve path aliases and package info loading - Replace relative imports with @ path alias for consistency - Read package info directly from package.json instead of env vars - Remove express body-parser middleware (use NestJS built-in) - Clean up commented code in app.module.ts - Enable no-console ESLint rule with proper exceptions - Add APP_NAME constant for logger configuration --- .vscode/settings.json | 5 +- Dockerfile | 4 +- eslint.config.js | 9 +--- src/app.controller.ts | 6 +-- src/app.module.ts | 54 ++++--------------- src/app.service.ts | 2 +- src/common/filters/all-exceptions.filter.ts | 2 +- .../request-context.interceptor.ts | 2 +- src/common/logger.service.ts | 3 ++ src/main.ts | 15 ++---- src/utils/constants.ts | 7 ++- src/utils/helpers/console-formatter.ts | 3 +- tsconfig.json | 9 ++-- 13 files changed, 43 insertions(+), 78 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 47829c7..cba2415 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,5 +33,8 @@ "files.trimTrailingWhitespace": true, // 保存时移除行尾空白 "files.insertFinalNewline": true, // 文件末尾自动插入换行符 "files.encoding": "utf8", // 文件编码:UTF-8 - "files.eol": "\n" // 行结束符:Unix 风格(\n) + "files.eol": "\n", // 行结束符:Unix 风格(\n) + + // ===== NPM 配置 ===== + "npm.exclude": "**/dist" // 排除 dist 目录下的 package.json } diff --git a/Dockerfile b/Dockerfile index 73956e6..ef4c103 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ COPY package.json ./ # 构建参数 ARG GIT_COMMIT=unknown -ARG APP_VERSION=0.0.0 +ARG APP_VERSION ARG NODE_ENV=production ARG PORT=3000 @@ -74,6 +74,8 @@ ENV NODE_ENV=$NODE_ENV ENV npm_package_version=$APP_VERSION ENV PORT=$PORT +EXPOSE ${PORT} + # 健康检查 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD curl --fail http://localhost:${PORT}/health diff --git a/eslint.config.js b/eslint.config.js index b6e8e40..bb3e366 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -33,18 +33,11 @@ export default [ ...tsPlugin.configs.recommended.rules, // TypeScript 特定规则 - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - }, - ], '@typescript-eslint/no-explicit-any': 'off', // 允许显式 any '@typescript-eslint/explicit-module-boundary-types': 'off', // 灵活 // 风格规则 - // 'no-console': 'warn', // 生产环境应清理 console + 'no-console': 'warn', // 生产环境应清理 console 'no-debugger': 'error', // debugger 不能提交 'prefer-const': 'error', // 优先 const 'no-var': 'error', // 禁用 var diff --git a/src/app.controller.ts b/src/app.controller.ts index 1dcd652..2f0eb90 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,8 +1,8 @@ import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service.js'; +import { AppService } from '@/app.service.js'; import { Body, Post, HttpStatus, HttpException } from '@nestjs/common'; -import { BusinessException } from './common/exceptions/business.exception.js'; -import { LoginDto } from './app.dto.js'; +import { BusinessException } from '@/common/exceptions/business.exception.js'; +import { LoginDto } from '@/app.dto.js'; import { Logger } from '@/common/logger.service.js'; import { PinoLogger } from 'nestjs-pino'; import { DatabaseService } from '@/common/database.service.js'; diff --git a/src/app.module.ts b/src/app.module.ts index 8888f5f..81c7772 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,23 +1,23 @@ import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { AppController, TestController } from './app.controller.js'; -import { AppService } from './app.service.js'; -import { DatabaseService } from './common/database.service.js'; +import { AppController, TestController } from '@/app.controller.js'; +import { AppService } from '@/app.service.js'; +import { DatabaseService } from '@/common/database.service.js'; import { APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_GUARD } from '@nestjs/core'; import { ZodValidationPipe, ZodSerializerInterceptor } from 'nestjs-zod'; -import { AllExceptionsFilter } from './common/filters/all-exceptions.filter.js'; +import { AllExceptionsFilter } from '@/common/filters/all-exceptions.filter.js'; import { PerformanceInterceptor, RequestContextInterceptor, ResponseFormatInterceptor, TimeoutInterceptor, -} from './common/interceptors/index.js'; +} from '@/common/interceptors/index.js'; import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { LoggerModule } from 'nestjs-pino'; import pino from 'pino'; -import { IS_DEV, IS_PROD } from './utils/constants.js'; +import { IS_DEV, IS_PROD, APP_NAME } from '@/utils/constants.js'; import { Logger } from '@/common/logger.service.js'; -import { RequestPreprocessingMiddleware } from './common/middleware/request-preprocessing.middleware.js'; +import { RequestPreprocessingMiddleware } from '@/common/middleware/request-preprocessing.middleware.js'; import { z } from 'zod/v4'; @Module({ @@ -184,7 +184,7 @@ import { z } from 'zod/v4'; LoggerModule.forRoot({ pinoHttp: [ { - name: process.env.npm_package_name, + name: APP_NAME, level: process.env.LOG_LEVEL || (!IS_PROD ? 'trace' : 'info'), // prettier-ignore transport: @@ -194,41 +194,12 @@ import { z } from 'zod/v4'; sync: true, colorize: true, translateTime: 'SYS:yyyy-mm-dd HH:MM:ss.l', - messageFormat: '{if req.method}[{req.method}]({req.url}){end} {if context}{context} - {end}{msg}', + // messageFormat: '{if req.method}[{req.method}]({req.url}){end} {if context}{context} - {end}{msg}', }, } : undefined, serializers: { err: () => undefined, // 错误堆栈交由 exceptions.filter 处理,避免重复记录 - // 请求序列化 - // req: (req) => { - // return { - // id: req.id, - // method: req.method, - // url: req.url, - // query: req.query, - // params: req.params, - // // 只记录部分关键 headers - // headers: { - // 'user-agent': req.headers['user-agent'], - // 'content-type': req.headers['content-type'], - // authorization: req.headers['authorization'], - // }, - // remoteAddress: req.remoteAddress, - // remotePort: req.remotePort, - // }; - // }, req: () => undefined, // 请求信息交由 performance.interceptor 处理,避免重复记录 - // 响应序列化 - // res: (res) => { - // return { - // statusCode: res.statusCode, - // // 只记录关键响应头 - // headers: { - // 'content-type': res.headers['content-type'], - // 'content-length': res.headers['content-length'], - // }, - // }; - // }, }, // prettier-ignore // 全局隐藏敏感信息 @@ -245,13 +216,6 @@ import { z } from 'zod/v4'; mkdir: true, }), ], - // 排除的日志记录路径和方法 - // exclude: [ - // { path: '/hello', method: RequestMethod.ALL }, - // { path: '/health', method: RequestMethod.ALL }, - // { path: '/logger/*', method: RequestMethod.ALL }, - // { path: '/perf-test/*', method: RequestMethod.ALL }, - // ], }), ], controllers: [AppController, TestController], diff --git a/src/app.service.ts b/src/app.service.ts index 997a6b8..9dd364b 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { uptime } from 'node:process'; -import { DatabaseService } from './common/database.service.js'; +import { DatabaseService } from '@/common/database.service.js'; import { ConfigService } from '@nestjs/config'; import { Logger } from '@/common/logger.service.js'; diff --git a/src/common/filters/all-exceptions.filter.ts b/src/common/filters/all-exceptions.filter.ts index 236b14e..aee3cd1 100644 --- a/src/common/filters/all-exceptions.filter.ts +++ b/src/common/filters/all-exceptions.filter.ts @@ -5,7 +5,7 @@ import { PrismaClientKnownRequestError } from '@root/prisma/generated/internal/p import { ZodValidationException, ZodSerializationException } from 'nestjs-zod'; import { ZodError } from 'zod/v4'; import { ThrottlerException } from '@nestjs/throttler'; -import { Logger } from '../logger.service.js'; +import { Logger } from '@/common/logger.service.js'; interface Request extends originRequest { user?: any; diff --git a/src/common/interceptors/request-context.interceptor.ts b/src/common/interceptors/request-context.interceptor.ts index c2d74ed..d12d864 100644 --- a/src/common/interceptors/request-context.interceptor.ts +++ b/src/common/interceptors/request-context.interceptor.ts @@ -1,6 +1,6 @@ import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; -import { RequestContextService } from '../request-context.service.js'; +import { RequestContextService } from '@/common/request-context.service.js'; import { Request } from '@/common/middleware/request-preprocessing.middleware.js'; /** diff --git a/src/common/logger.service.ts b/src/common/logger.service.ts index 9175d3d..5dbb503 100644 --- a/src/common/logger.service.ts +++ b/src/common/logger.service.ts @@ -48,6 +48,7 @@ export class Logger extends NestLogger { }; let formatData = ConsoleFormatter.format(level, payload, context); if (IS_DEV) formatData = JSON.parse(formatData); + /* eslint-disable no-console */ switch (level) { case 'verbose': console.debug(formatData); @@ -66,6 +67,7 @@ export class Logger extends NestLogger { console.error(formatData); break; } + /* eslint-enable no-console */ const expectionStack = err.stack ?? 'No stack trace available'; const selfExpectionPayload = { context: 'Logger', @@ -82,6 +84,7 @@ export class Logger extends NestLogger { `Internal error\n${expectionStack}` ); if (IS_DEV) selfExceptionData = JSON.parse(selfExceptionData); + // eslint-disable-next-line no-console console.error(selfExceptionData); } } diff --git a/src/main.ts b/src/main.ts index 2a2392d..f96b077 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,17 +1,18 @@ import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module.js'; +import { AppModule } from '@/app.module.js'; import figlet from 'figlet'; import { atlas } from 'gradient-string'; import compression from 'compression'; -import express from 'express'; import { Logger as pinoLogger } from 'nestjs-pino'; import { Logger } from '@/common/logger.service.js'; import helmet from 'helmet'; +import { APP_VERSION } from '@/utils/constants.js'; + async function bootstrap() { const app = await NestFactory.create(AppModule, { bufferLogs: true }); app.useLogger(app.get(pinoLogger)); @@ -48,9 +49,6 @@ async function bootstrap() { maxAge: 86400, }); - app.use(express.json({ limit: '10mb' })); - app.use(express.urlencoded({ extended: true, limit: '10mb' })); - app.use(compression({ threshold: 1024 })); const port = parseInt(process.env.PORT ?? '3000'); @@ -84,13 +82,10 @@ bootstrap() font: 'Slant', horizontalLayout: 'fitted', }); - process.stdout.write( - atlas.multiline( - startupBanner + `\nv${process.env.npm_package_version || '0.0.0'} | by FOV-RGT\n\n` - ) - ); + process.stdout.write(atlas.multiline(startupBanner + `\nv${APP_VERSION} | by FOV-RGT\n\n`)); }) .catch((err) => { + // eslint-disable-next-line no-console console.error('Bootstrap failed:', err); process.exit(1); }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 6ee0504..528e8ec 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,8 +1,13 @@ import 'dotenv/config'; +import _package_info from '@root/package.json' with { type: 'json' }; export const DEFAULT_PORT = Number(process.env.PORT); -export const APP_VERSION = process.env.npm_package_version ?? '0.0.0'; +export const PACKAGE_INFO = _package_info; + +export const APP_VERSION = PACKAGE_INFO.version || '0.0.0'; + +export const APP_NAME = PACKAGE_INFO.name || 'nestjs-app'; export const IS_DEV = process.env.NODE_ENV === 'development'; diff --git a/src/utils/helpers/console-formatter.ts b/src/utils/helpers/console-formatter.ts index 1ea7ca2..3fe19a1 100644 --- a/src/utils/helpers/console-formatter.ts +++ b/src/utils/helpers/console-formatter.ts @@ -1,4 +1,5 @@ import os from 'os'; +import { APP_NAME } from '@/utils/constants.js'; type Message = string | Error | Record; @@ -18,7 +19,7 @@ export class ConsoleFormatter { time: Date.now(), pid: process.pid, hostname: os.hostname(), - name: process.env.npm_package_name, + name: APP_NAME, ...this.formatMsgAndCtx(message, context), }); } diff --git a/tsconfig.json b/tsconfig.json index 36516ca..05af714 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,9 @@ { "compilerOptions": { - // 模块系统:输出 ESNext 格式 - "module": "ESNext", + // 模块系统:输出 nodenext 格式 + "module": "nodenext", - // 模块解析策略:使用 Node.js 风格解析 - "moduleResolution": "node", + "moduleResolution": "nodenext", // 生成 .d.ts 声明文件(用于类型提示) "declaration": true, @@ -25,7 +24,7 @@ "esModuleInterop": true, // 编译目标:输出最新的 JavaScript 语法(兼容现代 Node.js) - "target": "ESNext", + "target": "esnext", // 生成 source map 文件(用于调试) "sourceMap": true, From 91fb5094250f3a78e58575216286fff017d38dd1 Mon Sep 17 00:00:00 2001 From: FOV-RGT Date: Mon, 16 Feb 2026 12:05:43 +0800 Subject: [PATCH 2/3] chore: bump version to 0.5.3 [snapshot] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33a0149..b1f997e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nestjs-demo-basic", "private": true, - "version": "0.5.2", + "version": "0.5.3", "description": "一个使用 NestJS 构建的示例项目", "license": "MIT", "type": "module", From 7e1243fe16d83572f0fc35d0024e2f08b2ac0863 Mon Sep 17 00:00:00 2001 From: FOV-RGT Date: Mon, 16 Feb 2026 12:44:47 +0800 Subject: [PATCH 3/3] refactor: optimize Docker image and improve version management - Remove unnecessary production dependencies (pnpm, package.json copy) - Move build-only dependencies (@nestjs/cli, prisma, tsc-alias) to devDependencies - Use environment variables (APP_VERSION, APP_NAME) instead of package.json - Remove timestamp field from health check response - Fix import paths with .js extensions in tests - Update CMD path in Dockerfile to use correct entry point --- Dockerfile | 13 ++++++------- package.json | 6 +++--- src/app.service.ts | 4 ++-- .../middleware/request-preprocessing.middleware.ts | 2 +- src/utils/constants.ts | 4 ++-- test/unit/app.controller.spec.ts | 8 +++----- test/unit/app.service.spec.ts | 6 ++---- test/unit/prisma.service.spec.ts | 2 +- 8 files changed, 20 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index ef4c103..c01b231 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,7 @@ RUN pnpm prune --prod --ignore-scripts FROM node:22-slim # 安装依赖和 OpenSSL (运行时 Prisma Client 可能需要) -RUN apt-get update -y && apt-get install -y openssl curl && rm -rf /var/lib/apt/lists/* && npm install -g pnpm +RUN apt-get update -y && apt-get install -y openssl curl && rm -rf /var/lib/apt/lists/* # 设置工作目录 WORKDIR /app @@ -59,19 +59,18 @@ COPY --from=builder /app/node_modules ./node_modules # 从构建阶段复制构建输出 COPY --from=builder /app/dist ./dist -# 复制 package.json (用于识别项目信息) -COPY package.json ./ - # 构建参数 -ARG GIT_COMMIT=unknown ARG APP_VERSION +ARG APP_NAME ARG NODE_ENV=production +ARG GIT_COMMIT=unknown ARG PORT=3000 # 环境变量 +ENV APP_VERSION=$APP_VERSION +ENV APP_NAME=$APP_NAME ENV GIT_COMMIT=$GIT_COMMIT ENV NODE_ENV=$NODE_ENV -ENV npm_package_version=$APP_VERSION ENV PORT=$PORT EXPOSE ${PORT} @@ -80,4 +79,4 @@ EXPOSE ${PORT} HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD curl --fail http://localhost:${PORT}/health # 启动应用 -CMD ["node", "dist/src/main.js"] +CMD ["node", "dist/src/main"] diff --git a/package.json b/package.json index b1f997e..e624b7b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "pnpm": ">=8.0.0" }, "dependencies": { - "@nestjs/cli": "^11.0.16", "@nestjs/common": "^11.1.13", "@nestjs/config": "^4.0.3", "@nestjs/core": "^11.1.13", @@ -46,15 +45,14 @@ "pg": "^8.18.0", "pino": "^10.3.1", "pino-http": "^11.0.0", - "prisma": "^7.3.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", - "tsc-alias": "^1.8.16", "ulid": "^3.0.2", "zod": "^4.3.6" }, "devDependencies": { "@eslint/js": "^9.39.2", + "@nestjs/cli": "^11.0.16", "@nestjs/testing": "^11.1.13", "@types/compression": "^1.8.1", "@types/express": "^5.0.6", @@ -73,8 +71,10 @@ "lint-staged": "^16.2.7", "pino-pretty": "^13.1.3", "prettier": "^3.8.1", + "prisma": "^7.3.0", "supertest": "^7.2.2", "ts-jest": "^29.4.6", + "tsc-alias": "^1.8.16", "typescript": "^5.9.3" }, "lint-staged": { diff --git a/src/app.service.ts b/src/app.service.ts index 9dd364b..2d85e7b 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -3,6 +3,7 @@ import { uptime } from 'node:process'; import { DatabaseService } from '@/common/database.service.js'; import { ConfigService } from '@nestjs/config'; import { Logger } from '@/common/logger.service.js'; +import { APP_VERSION } from '@/utils/constants.js'; @Injectable() export class AppService { @@ -21,9 +22,8 @@ export class AppService { const databaseHealth = await this.checkDatabaseHealth(); return { status: 'ok', - timestamp: new Date().toISOString(), uptime: uptime(), - version: this.configService.get('npm_package_version', 'N/A'), + version: APP_VERSION, gitCommit: this.configService.get('GIT_COMMIT', 'N/A'), components: { database: { diff --git a/src/common/middleware/request-preprocessing.middleware.ts b/src/common/middleware/request-preprocessing.middleware.ts index df832d3..486181a 100644 --- a/src/common/middleware/request-preprocessing.middleware.ts +++ b/src/common/middleware/request-preprocessing.middleware.ts @@ -14,7 +14,7 @@ export class RequestPreprocessingMiddleware implements NestMiddleware { const reqId = req.headers['x-request-id'] ?? ulid(); req.id = typeof reqId === 'string' ? reqId : reqId[0]; res.setHeader('X-Request-Id', req.id); - req.version = APP_VERSION ?? 'unknown'; + req.version = APP_VERSION; next(); } } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 528e8ec..7b158c3 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -5,9 +5,9 @@ export const DEFAULT_PORT = Number(process.env.PORT); export const PACKAGE_INFO = _package_info; -export const APP_VERSION = PACKAGE_INFO.version || '0.0.0'; +export const APP_VERSION = process.env.APP_VERSION || PACKAGE_INFO.version || 'unknown'; -export const APP_NAME = PACKAGE_INFO.name || 'nestjs-app'; +export const APP_NAME = process.env.APP_NAME || PACKAGE_INFO.name || 'unknown'; export const IS_DEV = process.env.NODE_ENV === 'development'; diff --git a/test/unit/app.controller.spec.ts b/test/unit/app.controller.spec.ts index 8702c70..6ddc87a 100644 --- a/test/unit/app.controller.spec.ts +++ b/test/unit/app.controller.spec.ts @@ -1,6 +1,6 @@ -import { AppController } from '@/app.controller'; -import { AppService } from '@/app.service'; -import { DatabaseService } from '@/common/database.service'; +import { AppController } from '@/app.controller.js'; +import { AppService } from '@/app.service.js'; +import { DatabaseService } from '@/common/database.service.js'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { PinoLogger } from 'nestjs-pino'; @@ -52,7 +52,5 @@ describe('AppController (unit)', () => { it('getHealth should return status ok and timestamp', async () => { const res: any = await controller.getHealth(); expect(res).toHaveProperty('status', 'ok'); - expect(res).toHaveProperty('timestamp'); - expect(new Date(res.timestamp).toString()).not.toContain('Invalid'); }); }); diff --git a/test/unit/app.service.spec.ts b/test/unit/app.service.spec.ts index adcc755..afbc587 100644 --- a/test/unit/app.service.spec.ts +++ b/test/unit/app.service.spec.ts @@ -1,5 +1,5 @@ -import { AppService } from '@/app.service'; -import { DatabaseService } from '@/common/database.service'; +import { AppService } from '@/app.service.js'; +import { DatabaseService } from '@/common/database.service.js'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { PinoLogger } from 'nestjs-pino'; @@ -48,7 +48,5 @@ describe('AppService', () => { it('getHealth should return status ok and timestamp', async () => { const res: any = await service.getHealth(); expect(res).toHaveProperty('status', 'ok'); - expect(res).toHaveProperty('timestamp'); - expect(new Date(res.timestamp).toString()).not.toContain('Invalid'); }); }); diff --git a/test/unit/prisma.service.spec.ts b/test/unit/prisma.service.spec.ts index fc254b8..f01e7b3 100644 --- a/test/unit/prisma.service.spec.ts +++ b/test/unit/prisma.service.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { DatabaseService } from '@/common/database.service'; +import { DatabaseService } from '@/common/database.service.js'; describe('DatabaseService', () => { let service: DatabaseService;