diff --git a/wwjcloud-nest-v1/.husky/pre-commit b/wwjcloud-nest-v1/.husky/pre-commit new file mode 100644 index 00000000..72c4429b --- /dev/null +++ b/wwjcloud-nest-v1/.husky/pre-commit @@ -0,0 +1 @@ +npm test diff --git a/wwjcloud-nest-v1/package.json b/wwjcloud-nest-v1/package.json index c17d4194..2ea2af51 100644 --- a/wwjcloud-nest-v1/package.json +++ b/wwjcloud-nest-v1/package.json @@ -3,5 +3,8 @@ "@types/node": "^24.10.0", "glob": "^11.0.3", "typescript": "^5.9.3" + }, + "scripts": { + "prepare": "husky" } } diff --git a/wwjcloud-nest-v1/wwjcloud/.husky/pre-commit b/wwjcloud-nest-v1/wwjcloud/.husky/pre-commit new file mode 100644 index 00000000..4b9e48c0 --- /dev/null +++ b/wwjcloud-nest-v1/wwjcloud/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# 质量门禁 pre-commit hook +# 仅对暂存区的 TypeScript 文件执行 lint-staged +npx lint-staged diff --git a/wwjcloud-nest-v1/wwjcloud/eslint.config.mjs b/wwjcloud-nest-v1/wwjcloud/eslint.config.mjs index 258e0c3e..3e9fd604 100644 --- a/wwjcloud-nest-v1/wwjcloud/eslint.config.mjs +++ b/wwjcloud-nest-v1/wwjcloud/eslint.config.mjs @@ -108,6 +108,50 @@ export default tseslint.config( 'no-empty': 'warn', }, }, + // ======================================================================== + // 新增代码(generator/runtime/skills/memory/providers/vendor-gateway) + // any 相关规则升级为 error — 严格类型安全 + // ======================================================================== + { + files: [ + 'libs/wwjcloud-ai/src/generator/**/*.ts', + 'libs/wwjcloud-ai/src/runtime/**/*.ts', + 'libs/wwjcloud-ai/src/skills/**/*.ts', + 'libs/wwjcloud-ai/src/memory/**/*.ts', + 'libs/wwjcloud-ai/src/providers/**/*.ts', + 'libs/wwjcloud-boot/src/vendor/gateway/**/*.ts', + 'libs/wwjcloud-boot/src/vendor/registry/**/*.ts', + 'libs/wwjcloud-boot/src/vendor/interfaces/**/*.ts', + 'libs/wwjcloud-boot/src/vendor/errors/**/*.ts', + 'libs/wwjcloud-boot/src/vendor/provider-factories/**/*.ts', + ], + rules: { + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unsafe-assignment': 'error', + '@typescript-eslint/no-unsafe-call': 'error', + '@typescript-eslint/no-unsafe-member-access': 'error', + '@typescript-eslint/no-unsafe-return': 'error', + '@typescript-eslint/no-unsafe-argument': 'error', + '@typescript-eslint/no-unsafe-enum-comparison': 'error', + '@typescript-eslint/no-base-to-string': 'error', + '@typescript-eslint/restrict-template-expressions': 'error', + '@typescript-eslint/no-redundant-type-constituents': 'error', + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/require-await': 'error', + '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/no-require-imports': 'error', + 'no-case-declarations': 'error', + 'no-empty': 'error', + 'no-useless-catch': 'error', + 'no-useless-escape': 'error', + }, + }, + // ======================================================================== + // 全局默认规则(兜底 — 旧代码为 warn 级别) + // ======================================================================== { rules: { '@typescript-eslint/no-explicit-any': 'warn', diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-ai/src/generator/controller.generator.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-ai/src/generator/controller.generator.ts index dcc12607..da3aaca0 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-ai/src/generator/controller.generator.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-ai/src/generator/controller.generator.ts @@ -84,6 +84,7 @@ ${methodsCode} ); const params = this.extractParams(method); const paramName = this.toCamelCase(method.name); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const returnType = 'Promise'; return ` /** @@ -130,6 +131,7 @@ ${methodsCode} } else if (method.httpMethod === 'GET') { parts.push(`@Query('${param}') ${param}: string`); } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any parts.push(`@Body('${param}') ${param}: any`); } } diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/param-decorators.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/param-decorators.ts new file mode 100644 index 00000000..a8b35238 --- /dev/null +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/param-decorators.ts @@ -0,0 +1,198 @@ +/** + * 自定义参数装饰器模块 + * + * 充分发挥 NestJS 自定义参数装饰器特性,从 RequestContextService (AsyncLocalStorage) + * 中提取请求上下文信息,避免控制器中手动注入 RequestContextService。 + * + * 使用方式: + * @CurrentUser() user: UserClaims + * @SiteId() siteId: number + * @MemberId() memberId: number + * @ReqId() requestId: string + * @CurrentLang() lang: string + * @CurrentChannel() channel: string + * @ClientIp() ip: string + * @UserAgent() ua: string + */ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { + REQUEST_CONTEXT_KEY, + type RequestContextStore, +} from './request-context.service'; + +/** + * 从 Express Request 对象中提取请求上下文 + */ +function getStore( + ctx: ExecutionContext, +): RequestContextStore | undefined { + const request = ctx.switchToHttp().getRequest(); + return request[REQUEST_CONTEXT_KEY] as RequestContextStore | undefined; +} + +/** + * 从请求上下文中提取当前用户信息 + * + * @example + * ```typescript + * @Get('profile') + * getProfile(@CurrentUser() user: { userId?: string; username?: string; roles?: string[] }) { + * return user; + * } + * ``` + */ +export const CurrentUser = createParamDecorator( + (_data: unknown, ctx: ExecutionContext) => { + const rcs = getStore(ctx); + if (!rcs) return undefined; + return { + userId: rcs.userId, + username: rcs.username, + roles: rcs.roles, + }; + }, +); + +/** + * 从请求上下文中提取当前站点ID(数值类型) + * + * @example + * ```typescript + * @Get('config') + * getConfig(@SiteId() siteId: number) { + * return this.service.getConfig(siteId); + * } + * ``` + */ +export const SiteId = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): number => { + const rcs = getStore(ctx); + return rcs?.siteId ? Number(rcs.siteId) : 0; + }, +); + +/** + * 从请求上下文中提取当前站点ID(字符串类型) + * + * @example + * ```typescript + * @Get('info') + * getInfo(@SiteIdStr() siteId: string) { + * return this.service.getInfo(siteId); + * } + * ``` + */ +export const SiteIdStr = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string | undefined => { + const rcs = getStore(ctx); + return rcs?.siteId; + }, +); + +/** + * 从请求上下文中提取当前会员ID + * + * @example + * ```typescript + * @Get('orders') + * getOrders(@MemberId() memberId: number) { + * return this.orderService.getByMember(memberId); + * } + * ``` + */ +export const MemberId = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): number | undefined => { + const rcs = getStore(ctx); + return rcs?.memberId; + }, +); + +/** + * 从请求上下文中提取请求ID(链路追踪) + * + * @example + * ```typescript + * @Post('submit') + * submit(@ReqId() requestId: string, @Body() dto: SubmitDto) { + * this.logger.log(`[${requestId}] Processing submission`); + * } + * ``` + */ +export const ReqId = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string | undefined => { + const rcs = getStore(ctx); + return rcs?.requestId; + }, +); + +/** + * 从请求上下文中提取当前语言 + * + * @example + * ```typescript + * @Get('content') + * getContent(@CurrentLang() lang: string) { + * return this.i18nService.translate('hello', { lang }); + * } + * ``` + */ +export const CurrentLang = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string | undefined => { + const rcs = getStore(ctx); + return rcs?.lang; + }, +); + +/** + * 从请求上下文中提取当前渠道(h5/weapp/pc 等) + * + * @example + * ```typescript + * @Get('page') + * getPage(@CurrentChannel() channel: string) { + * return this.diyService.getPage(channel); + * } + * ``` + */ +export const CurrentChannel = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string | undefined => { + const rcs = getStore(ctx); + return rcs?.channel; + }, +); + +/** + * 从请求上下文中提取客户端IP + * + * @example + * ```typescript + * @Post('login') + * login(@ClientIp() ip: string, @Body() dto: LoginDto) { + * return this.authService.login(dto, ip); + * } + * ``` + */ +export const ClientIp = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string | undefined => { + const rcs = getStore(ctx); + return rcs?.ip; + }, +); + +/** + * 从请求上下文中提取 User-Agent + * + * @example + * ```typescript + * @Get('device') + * getDeviceInfo(@UserAgent() ua: string) { + * return this.deviceService.parse(ua); + * } + * ``` + */ +export const UserAgent = createParamDecorator( + (_data: unknown, ctx: ExecutionContext): string | undefined => { + const rcs = getStore(ctx); + return rcs?.ua; + }, +); diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.middleware.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.middleware.ts index 835398a6..43f97c69 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.middleware.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.middleware.ts @@ -41,20 +41,22 @@ export function buildRequestContextMiddleware(ctx: RequestContextService) { undefined; const ua = (req.headers['user-agent'] as string) || undefined; - ctx.runWith( - { - requestId: id, - siteId, - userId, - username, - roles, - lang, - channel, - appType, - ip, - ua, - }, - () => next(), - ); + const store = { + requestId: id, + siteId, + userId, + username, + roles, + lang, + channel, + appType, + ip, + ua, + }; + + // 将上下文绑定到 req 对象,供自定义参数装饰器使用 + ctx.bindToRequest(req as unknown as Record); + + ctx.runWith(store, () => next()); }; } diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.service.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.service.ts index 19460a7b..35a31a30 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.service.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/infra/http/request-context.service.ts @@ -2,7 +2,8 @@ import { Injectable } from '@nestjs/common'; import { AsyncLocalStorage } from 'async_hooks'; import { ThreadLocalHolder } from '../context/thread-local-holder'; -interface RequestContextStore { +/** 请求上下文存储结构 */ +export interface RequestContextStore { requestId?: string; siteId?: string; memberId?: number; @@ -16,6 +17,11 @@ interface RequestContextStore { ua?: string; } +/** + * 挂载到 Express Request 对象上的 key,供自定义参数装饰器使用 + */ +export const REQUEST_CONTEXT_KEY = '__wwj_request_context'; + @Injectable() export class RequestContextService { private readonly storage = new AsyncLocalStorage(); @@ -27,6 +33,14 @@ export class RequestContextService { }); } + /** + * 将当前上下文绑定到 Express Request 对象上 + * 供自定义参数装饰器(@CurrentUser, @SiteId 等)使用 + */ + bindToRequest(req: Record): void { + req[REQUEST_CONTEXT_KEY] = this.storage.getStore(); + } + getContext(): RequestContextStore | undefined { return this.storage.getStore(); } diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/handler-provider.factory.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/handler-provider.factory.ts index 67d4fa31..92e924d5 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/handler-provider.factory.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/handler-provider.factory.ts @@ -5,7 +5,7 @@ import { FactoryProvider, } from '@nestjs/common'; -export interface HandlerProvider { +export interface HandlerProvider { handle(bean: M): R | Promise; } @@ -16,14 +16,14 @@ export const DEFAULT_HANDLER_PROVIDER = 'DEFAULT_HANDLER_PROVIDER'; export class HandlerProviderFactory { private handlerProviderMap = new Map< string, - Set HandlerProvider> + Set HandlerProvider> >(); private handlerProviderClassMap = new Map>(); private handlerProviderModelMap = new Map>(); register( name: string, - handlerProviderClass: new (...args: any[]) => T, + handlerProviderClass: new (...args: unknown[]) => T, modelType?: string, ) { // 根据名称注册 @@ -48,6 +48,7 @@ export class HandlerProviderFactory { // 同步调用处理器 async invoke(bean: M): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const beanType = (bean as any).constructor?.name || 'Unknown'; const handlerProviderClassSet = this.handlerProviderMap.get(beanType); const resultList: R[] = []; @@ -57,7 +58,7 @@ export class HandlerProviderFactory { try { const handlerProvider = new HandlerProviderClass(); const result = await handlerProvider.handle(bean); - resultList.push(result); + resultList.push(result as R); } catch (error) { console.error( `Error invoking handler ${HandlerProviderClass.name}:`, @@ -82,7 +83,7 @@ export class HandlerProviderFactory { getHandlerProviderMap(): Map< string, - Set HandlerProvider> + Set HandlerProvider> > { return new Map(this.handlerProviderMap); } diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/aliyun-sms.provider.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/aliyun-sms.provider.ts index 07d1310b..28abcea2 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/aliyun-sms.provider.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/aliyun-sms.provider.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/require-await */ import { Injectable, Logger } from '@nestjs/common'; import { SmsSendParams, diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/local-upload.provider.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/local-upload.provider.ts index 8b0b50ab..09f8da66 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/local-upload.provider.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/local-upload.provider.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/require-await */ import { Injectable, Logger } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/wechat-pay.provider.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/wechat-pay.provider.ts index fe89241a..045fcbdb 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/wechat-pay.provider.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/impls/wechat-pay.provider.ts @@ -210,6 +210,7 @@ export class WechatPayProvider } /** 健康检查:验证微信支付是否已配置 */ + // eslint-disable-next-line @typescript-eslint/require-await async healthCheck(): Promise { const start = Date.now(); const configured = !!(this.appId && this.mchId && this.apiKey); diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/job-provider.factory.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/job-provider.factory.ts index 4eb91ef2..7e30b8a8 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/job-provider.factory.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/job-provider.factory.ts @@ -10,7 +10,7 @@ export interface JobExecutionContext { jobGroup: string; fireTime: Date; scheduledFireTime: Date; - data: Record; + data: Record; } export interface JobProvider { @@ -32,13 +32,13 @@ export const DEFAULT_JOB_PROVIDER = 'DEFAULT_JOB_PROVIDER'; export class JobProviderFactory { private jobProviderClassMap = new Map< string, - new (...args: any[]) => JobProvider + new (...args: unknown[]) => JobProvider >(); private jobProviderNameMap = new Map>(); register( key: string, - jobProviderClass: new (...args: any[]) => JobProvider, + jobProviderClass: new (...args: unknown[]) => JobProvider, source?: string, ) { this.jobProviderClassMap.set(key, jobProviderClass); @@ -50,7 +50,7 @@ export class JobProviderFactory { } } - getJobProvider(key: string): new (...args: any[]) => JobProvider { + getJobProvider(key: string): new (...args: unknown[]) => JobProvider { const jobProviderClass = this.jobProviderClassMap.get(key); if (!jobProviderClass) { throw new Error(`Job provider not found: ${key}`); @@ -63,7 +63,10 @@ export class JobProviderFactory { return new JobProviderClass(); } - getJobProviderClassMap(): Map JobProvider> { + getJobProviderClassMap(): Map< + string, + new (...args: unknown[]) => JobProvider + > { return new Map(this.jobProviderClassMap); } diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/loader-provider.factory.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/loader-provider.factory.ts index f48e7804..fe388b92 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/loader-provider.factory.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/loader-provider.factory.ts @@ -8,11 +8,11 @@ import * as fs from 'fs'; import * as path from 'path'; export interface LoaderProvider { - loadJSON(addon: string, location: string): any; - loadJSONObject(addon: string, location: string): Record; - loadJSONArray(addon: string, location: string): any[]; - mergeJSONObject(location: string): Record; - mergeJSONArray(location: string): any[]; + loadJSON(addon: string, location: string): unknown; + loadJSONObject(addon: string, location: string): Record; + loadJSONArray(addon: string, location: string): unknown[]; + mergeJSONObject(location: string): Record; + mergeJSONArray(location: string): unknown[]; } export const LOADER_PROVIDERS = 'LOADER_PROVIDERS'; @@ -21,7 +21,7 @@ export const DEFAULT_LOADER_PROVIDER = 'DEFAULT_LOADER_PROVIDER'; class LoaderProviderImpl implements LoaderProvider { private basePath = process.cwd(); - loadJSON(addon: string, location: string): any { + loadJSON(addon: string, location: string): unknown { try { const filePath = this.resolveFilePath(addon, location); const content = fs.readFileSync(filePath, 'utf-8'); @@ -32,19 +32,21 @@ class LoaderProviderImpl implements LoaderProvider { } } - loadJSONObject(addon: string, location: string): Record { + loadJSONObject(addon: string, location: string): Record { const json = this.loadJSON(addon, location); - return json && typeof json === 'object' && !Array.isArray(json) ? json : {}; + return json && typeof json === 'object' && !Array.isArray(json) + ? (json as Record) + : {}; } - loadJSONArray(addon: string, location: string): any[] { + loadJSONArray(addon: string, location: string): unknown[] { const json = this.loadJSON(addon, location); return Array.isArray(json) ? json : []; } - mergeJSONObject(location: string): Record { + mergeJSONObject(location: string): Record { try { - const merged: Record = {}; + const merged: Record = {}; const addonsDir = path.join(this.basePath, 'addon'); if (!fs.existsSync(addonsDir)) { @@ -62,25 +64,22 @@ class LoaderProviderImpl implements LoaderProvider { if (jsonObject && Object.keys(jsonObject).length > 0) { Object.assign(merged, jsonObject); } - } catch (error) { + } catch { // 忽略单个插件加载失败 continue; } } return merged; - } catch (error) { - console.error( - `Error merging JSON objects from location ${location}:`, - error, - ); + } catch { + console.error(`Error merging JSON objects from location ${location}:`); return {}; } } - mergeJSONArray(location: string): any[] { + mergeJSONArray(location: string): unknown[] { try { - const merged: any[] = []; + const merged: unknown[] = []; const addonsDir = path.join(this.basePath, 'addon'); if (!fs.existsSync(addonsDir)) { @@ -98,18 +97,15 @@ class LoaderProviderImpl implements LoaderProvider { if (Array.isArray(jsonArray) && jsonArray.length > 0) { merged.push(...jsonArray); } - } catch (error) { + } catch { // 忽略单个插件加载失败 continue; } } return merged; - } catch (error) { - console.error( - `Error merging JSON arrays from location ${location}:`, - error, - ); + } catch { + console.error(`Error merging JSON arrays from location ${location}:`); return []; } } diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/pay-provider.factory.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/pay-provider.factory.ts index 4ce991f4..15ede441 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/pay-provider.factory.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/pay-provider.factory.ts @@ -2,9 +2,9 @@ import { Injectable, Module } from '@nestjs/common'; import { DynamicModule } from '@nestjs/common'; export interface IPayProvider { - createOrder(params: Record): Promise; - refund(params: Record): Promise; - queryOrder(orderId: string): Promise; + createOrder(params: Record): Promise; + refund(params: Record): Promise; + queryOrder(orderId: string): Promise; } export interface PayProviderConfig { @@ -27,7 +27,7 @@ export class PayProviderFactory { */ static init( annotationImplClassList: Array< - { new (): IPayProvider } & { [key: string]: any } + { new (): IPayProvider } & { [key: string]: unknown } >, ): void { if (!annotationImplClassList || annotationImplClassList.length <= 0) { @@ -111,7 +111,8 @@ export class PayProviderFactory { */ getDefaultProvider(): IPayProvider | null { // 尝试获取第一个可用的提供者 - const firstProvider = PayProviderFactory.payProviderMap.keys().next().value; + const firstProvider = PayProviderFactory.payProviderMap.keys().next() + .value as string | undefined; if (firstProvider) { return PayProviderFactory.create(firstProvider); } @@ -121,7 +122,12 @@ export class PayProviderFactory { /** * 提取提供者配置(模拟注解解析) */ - private static extractProviderConfig(cls: any): PayProviderConfig | null { + private static extractProviderConfig(cls: { + new (...args: unknown[]): IPayProvider; + providerConfig?: PayProviderConfig; + source?: string; + name?: string; + }): PayProviderConfig | null { // 这里应该实现注解解析逻辑 // 简化版本,实际需要更复杂的反射机制 return { diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/sms-provider.factory.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/sms-provider.factory.ts index f3fb7d69..ad1180d6 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/sms-provider.factory.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/sms-provider.factory.ts @@ -9,7 +9,7 @@ export interface SmsProvider { send( phoneNumber: string, content: string, - config?: Record, + config?: Record, ): Promise<{ ok: boolean; messageId: string; diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upgrade-provider.factory.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upgrade-provider.factory.ts index ad428443..9d542fdc 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upgrade-provider.factory.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upgrade-provider.factory.ts @@ -16,13 +16,13 @@ export const DEFAULT_UPGRADE_PROVIDER = 'DEFAULT_UPGRADE_PROVIDER'; export class UpgradeProviderFactory { private upgradeProviderMap = new Map< string, - new (...args: any[]) => UpgradeProvider + new (...args: unknown[]) => UpgradeProvider >(); register( addon: string, version: string, - upgradeProviderClass: new (...args: any[]) => UpgradeProvider, + upgradeProviderClass: new (...args: unknown[]) => UpgradeProvider, ) { const key = addon + version; this.upgradeProviderMap.set(key, upgradeProviderClass); @@ -31,7 +31,7 @@ export class UpgradeProviderFactory { getUpgradeProvider( addon: string, version: string, - ): (new (...args: any[]) => UpgradeProvider) | undefined { + ): (new (...args: unknown[]) => UpgradeProvider) | undefined { const key = addon + version; return this.upgradeProviderMap.get(key); } @@ -72,7 +72,7 @@ export class UpgradeProviderFactory { getUpgradeProviderMap(): Map< string, - new (...args: any[]) => UpgradeProvider + new (...args: unknown[]) => UpgradeProvider > { return new Map(this.upgradeProviderMap); } diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upload-provider.factory.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upload-provider.factory.ts index c1db24ed..7fbb147a 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upload-provider.factory.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/provider-factories/upload-provider.factory.ts @@ -5,11 +5,27 @@ import { DynamicModule, } from '@nestjs/common'; +/** + * 上传文件描述接口 + * 对齐 Express.Multer.File 的核心字段 + */ +export interface UploadFileDescriptor { + fieldname: string; + originalname: string; + encoding: string; + mimetype: string; + destination: string; + filename: string; + path: string; + size: number; + buffer?: Buffer; +} + /** * 上传模型 */ export interface UploadModel { - uploadFile?: any; // Express.Multer.File + uploadFile?: UploadFileDescriptor; uploadType?: string; uploadFilePath?: string; uploadFileName?: string; @@ -84,7 +100,7 @@ export interface UploadProvider { * 初始化上传提供者 * 严格对齐Java: IUploadProvider.init(JSONObject configObject) */ - init(configObject: Record): void; + init(configObject: Record): void; /** * 获取访问URL @@ -183,7 +199,7 @@ export class UploadProviderFactory { } try { return new Constructor(); - } catch (error) { + } catch { return null; } } @@ -194,7 +210,7 @@ export class UploadProviderFactory { */ static createAndInit( name: string, - configObject: Record, + configObject: Record, ): UploadProvider | null { const provider = this.create(name); if (provider && typeof provider.init === 'function') { diff --git a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/registry/provider-registry.service.ts b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/registry/provider-registry.service.ts index 16a8bc69..ab216d99 100644 --- a/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/registry/provider-registry.service.ts +++ b/wwjcloud-nest-v1/wwjcloud/libs/wwjcloud-boot/src/vendor/registry/provider-registry.service.ts @@ -3,8 +3,6 @@ import { ProviderMetadata, ProviderHealthStatus, RegisteredProvider, - ProviderRegisteredEvent, - ProviderUnregisteredEvent, } from './provider-metadata.interface'; import { HealthCheckResult, @@ -49,12 +47,6 @@ export class ProviderRegistryService implements OnModuleDestroy { // 启动健康检查(借鉴 OpenClaw Heartbeat) this.startHealthCheck(name, metadata.healthCheckInterval); - const event: ProviderRegisteredEvent = { - name, - version: metadata.version, - capabilities: metadata.capabilities, - timestamp: Date.now(), - }; this.logger.log( `Provider 注册成功: ${name}@${metadata.version} (${metadata.capabilities.join(', ')})`, ); @@ -70,11 +62,6 @@ export class ProviderRegistryService implements OnModuleDestroy { this.stopHealthCheck(name); this.providers.delete(name); - const event: ProviderUnregisteredEvent = { - name, - reason, - timestamp: Date.now(), - }; this.logger.log(`Provider 注销: ${name} (原因: ${reason})`); } diff --git a/wwjcloud-nest-v1/wwjcloud/package.json b/wwjcloud-nest-v1/wwjcloud/package.json index 78eec916..a43922da 100644 --- a/wwjcloud-nest-v1/wwjcloud/package.json +++ b/wwjcloud-nest-v1/wwjcloud/package.json @@ -20,7 +20,19 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "di:ai": "node -r ts-node/register -r tsconfig-paths/register scripts/di-debug-ai-only.ts", - "di:full": "node -r ts-node/register -r tsconfig-paths/register scripts/di-debug.ts" + "di:full": "node -r ts-node/register -r tsconfig-paths/register scripts/di-debug.ts", + "quality-gate": "bash scripts/quality-gate.sh", + "quality-gate:tsc": "bash scripts/quality-gate.sh --tsc", + "quality-gate:lint": "bash scripts/quality-gate.sh --lint", + "quality-gate:build": "bash scripts/quality-gate.sh --build", + "quality-gate:any": "bash scripts/quality-gate.sh --any", + "prepare": "cd .. && husky wwjcloud/.husky" + }, + "lint-staged": { + "*.ts": [ + "eslint --fix --max-warnings=0", + "prettier --write" + ] }, "dependencies": { "@nestjs/axios": "^4.0.1", @@ -53,6 +65,7 @@ "joi": "^18.0.1", "jsonwebtoken": "^9.0.2", "kafkajs": "^2.2.4", + "module-alias": "^2.2.3", "mysql2": "^3.15.3", "nestjs-i18n": "^10.5.1", "passport-jwt": "^4.0.1", @@ -60,8 +73,7 @@ "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "swagger-ui-express": "^5.0.1", - "typeorm": "^0.3.27", - "module-alias": "^2.2.3" + "typeorm": "^0.3.27" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -79,7 +91,9 @@ "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", "globals": "^16.0.0", + "husky": "^9.1.7", "jest": "^30.0.0", + "lint-staged": "^16.4.0", "prettier": "^3.4.2", "source-map-support": "^0.5.21", "supertest": "^7.0.0", @@ -114,8 +128,7 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" - } - , + }, "_moduleAliases": { "@wwjBoot": "dist/libs/wwjcloud-boot/src", "@wwjCommon": "dist/libs/wwjcloud-boot/src/infra", diff --git a/wwjcloud-nest-v1/wwjcloud/scripts/quality-gate.sh b/wwjcloud-nest-v1/wwjcloud/scripts/quality-gate.sh new file mode 100755 index 00000000..c1c59c47 --- /dev/null +++ b/wwjcloud-nest-v1/wwjcloud/scripts/quality-gate.sh @@ -0,0 +1,334 @@ +#!/usr/bin/env bash +# ============================================================================ +# WWJCloud-Nest v1 质量门禁脚本 (Quality Gate) +# ============================================================================ +# 用法: +# ./scripts/quality-gate.sh # 运行全部检查 +# ./scripts/quality-gate.sh --tsc # 仅类型检查 +# ./scripts/quality-gate.sh --lint # 仅 ESLint 检查 +# ./scripts/quality-gate.sh --build # 仅构建检查 +# ./scripts/quality-gate.sh --any # 仅 any 类型计数 +# ./scripts/quality-gate.sh --fix # 自动修复后运行门禁 +# ============================================================================ + +set -euo pipefail + +# ---- 颜色定义 ---- +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# ---- 阈值配置 ---- +# any 类型计数阈值(新增代码目录) +ANY_THRESHOLD_NEW_CODE=0 +# any 类型计数阈值(旧代码目录,渐进降低) +ANY_THRESHOLD_OLD_CODE=99999 +# ESLint 错误数阈值 +ESLINT_ERROR_THRESHOLD=0 +# ESLint 警告数阈值(旧代码允许较多警告) +ESLINT_WARN_THRESHOLD=15000 + +# ---- 项目根目录 ---- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_ROOT" + +# ---- 新增代码目录(严格模式) ---- +NEW_CODE_DIRS=( + "libs/wwjcloud-ai/src/generator" + "libs/wwjcloud-ai/src/runtime" + "libs/wwjcloud-ai/src/skills" + "libs/wwjcloud-ai/src/memory" + "libs/wwjcloud-ai/src/providers" + "libs/wwjcloud-boot/src/vendor/gateway" + "libs/wwjcloud-boot/src/vendor/registry" + "libs/wwjcloud-boot/src/vendor/interfaces" + "libs/wwjcloud-boot/src/vendor/errors" + "libs/wwjcloud-boot/src/vendor/provider-factories" +) + +# ---- 旧代码目录(渐进模式) ---- +OLD_CODE_DIRS=( + "libs/wwjcloud-core/src" + "libs/wwjcloud-boot/src" + "libs/wwjcloud-ai/src/safe" + "libs/wwjcloud-ai/src/tuner" + "libs/wwjcloud-ai/src/manager" + "libs/wwjcloud-ai/src/healing" +) + +# ---- 统计变量 ---- +TOTAL_SCORE=0 +TOTAL_CHECKS=0 +FAILED_CHECKS=0 +RESULTS=() + +# ---- 工具函数 ---- + +# 打印质量门禁头部信息 +print_header() { + echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}" + echo -e "${BLUE}${BOLD} WWJCloud-Nest v1 质量门禁 (Quality Gate)${NC}" + echo -e "${BLUE}${BOLD} $(date '+%Y-%m-%d %H:%M:%S')${NC}" + echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}" + echo "" +} + +# 打印检查段落标题 +print_section() { + echo -e "${CYAN}${BOLD}▸ $1${NC}" + echo -e "${CYAN}───────────────────────────────────────────────────────────────────${NC}" +} + +# 记录检查通过 +pass_check() { + local name="$1" + local detail="$2" + TOTAL_SCORE=$((TOTAL_SCORE + 1)) + TOTAL_CHECKS=$((TOTAL_CHECKS + 1)) + RESULTS+=("✅ $name: $detail") + echo -e " ${GREEN}✅ PASS${NC} — $name: $detail" +} + +# 记录检查失败 +fail_check() { + local name="$1" + local detail="$2" + TOTAL_CHECKS=$((TOTAL_CHECKS + 1)) + FAILED_CHECKS=$((FAILED_CHECKS + 1)) + RESULTS+=("❌ $name: $detail") + echo -e " ${RED}❌ FAIL${NC} — $name: $detail" +} + +# 记录检查警告 +warn_check() { + local name="$1" + local detail="$2" + TOTAL_CHECKS=$((TOTAL_CHECKS + 1)) + RESULTS+=("⚠️ $name: $detail") + echo -e " ${YELLOW}⚠️ WARN${NC} — $name: $detail" +} + +# ---- 检查函数 ---- + +# TypeScript 类型检查(tsc --noEmit) +check_tsc() { + print_section "TypeScript 类型检查 (tsc --noEmit)" + + if npx tsc --noEmit 2>&1; then + pass_check "TypeScript" "0 类型错误" + else + local errors + errors=$(npx tsc --noEmit 2>&1 | grep -c "error TS" || true) + fail_check "TypeScript" "${errors} 个类型错误" + fi + echo "" +} + +# ESLint 代码质量检查 +check_eslint() { + print_section "ESLint 代码质量检查" + + local output + output=$(npx eslint "{src,libs,test}/**/*.ts" --format json 2>/dev/null || true) + + if [ -z "$output" ]; then + pass_check "ESLint" "0 错误, 0 警告" + echo "" + return + fi + + # 解析 ESLint JSON 输出 + local total_errors total_warnings + total_errors=$(echo "$output" | node -e " + const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8')); + let errors = 0, warnings = 0; + for (const file of data) { + for (const msg of file.messages) { + if (msg.severity === 2) errors++; + else if (msg.severity === 1) warnings++; + } + } + console.log(errors + ' ' + warnings); + " 2>/dev/null || echo "0 0") + + total_errors=$(echo "$total_errors" | awk '{print $1}') + total_warnings=$(echo "$total_errors" | awk '{print $2}') + + if [ "$total_errors" -le "$ESLINT_ERROR_THRESHOLD" ]; then + if [ "$total_warnings" -le "$ESLINT_WARN_THRESHOLD" ]; then + pass_check "ESLint" "${total_errors} 错误, ${total_warnings} 警告" + else + warn_check "ESLint" "${total_errors} 错误, ${total_warnings} 警告 (超过警告阈值 ${ESLINT_WARN_THRESHOLD})" + fi + else + fail_check "ESLint" "${total_errors} 错误 (超过阈值 ${ESLINT_ERROR_THRESHOLD}), ${total_warnings} 警告" + fi + echo "" +} + +# NestJS 构建检查 +check_build() { + print_section "NestJS 构建 (nest build)" + + if npx nest build 2>&1; then + pass_check "Build" "构建成功" + else + fail_check "Build" "构建失败" + fi + echo "" +} + +# TypeScript any 类型计数检查 +check_any() { + print_section "TypeScript any 类型计数" + + # ---- 新增代码 any 计数 ---- + local new_any_count=0 + local new_any_files="" + + for dir in "${NEW_CODE_DIRS[@]}"; do + if [ -d "$PROJECT_ROOT/$dir" ]; then + local result + result=$(grep -rn --include="*.ts" -E '(: any\b|as any\b||: any\)|: any,|: any;|: any =|: any\])' "$PROJECT_ROOT/$dir" 2>/dev/null || true) + if [ -n "$result" ]; then + local count + count=$(echo "$result" | wc -l) + new_any_count=$((new_any_count + count)) + new_any_files="${new_any_files}\n$(echo "$result" | head -20)" + fi + fi + done + + echo -e " ${BOLD}新增代码 any 计数:${NC}" + echo -e " 检查目录: ${NEW_CODE_DIRS[*]}" + echo -e " 发现: ${new_any_count} 处" + + if [ "$new_any_count" -le "$ANY_THRESHOLD_NEW_CODE" ]; then + pass_check "Any-新代码" "${new_any_count} 处 (阈值: ${ANY_THRESHOLD_NEW_CODE})" + else + fail_check "Any-新代码" "${new_any_count} 处 (超过阈值 ${ANY_THRESHOLD_NEW_CODE})" + if [ -n "$new_any_files" ]; then + echo -e "${YELLOW} 详情 (前20条):${NC}" + echo -e "$new_any_files" | head -20 | while read -r line; do + echo -e " ${YELLOW}→ $line${NC}" + done + fi + fi + echo "" + + # ---- 旧代码 any 计数(仅统计,不阻断) ---- + local old_any_count=0 + + for dir in "${OLD_CODE_DIRS[@]}"; do + if [ -d "$PROJECT_ROOT/$dir" ]; then + local result + result=$(grep -rc --include="*.ts" -E '(: any\b|as any\b||: any\)|: any,|: any;|: any =|: any\])' "$PROJECT_ROOT/$dir" 2>/dev/null || true) + if [ -n "$result" ]; then + while read -r count; do + old_any_count=$((old_any_count + count)) + done <<< "$result" + fi + fi + done + + echo -e " ${BOLD}旧代码 any 计数:${NC}" + echo -e " 检查目录: ${OLD_CODE_DIRS[*]}" + echo -e " 发现: ${old_any_count} 处 ${YELLOW}(渐进降低中,当前不阻断)${NC}" + echo "" + + if [ "$old_any_count" -gt "$ANY_THRESHOLD_OLD_CODE" ]; then + warn_check "Any-旧代码" "${old_any_count} 处 (超过渐进阈值 ${ANY_THRESHOLD_OLD_CODE})" + else + pass_check "Any-旧代码" "${old_any_count} 处 (渐进模式)" + fi + echo "" +} + +# ---- 主流程 ---- +main() { + print_header + + local run_all=true + local run_tsc=false + local run_lint=false + local run_build=false + local run_any=false + local auto_fix=false + + for arg in "$@"; do + case "$arg" in + --tsc) run_tsc=true; run_all=false ;; + --lint) run_lint=true; run_all=false ;; + --build) run_build=true; run_all=false ;; + --any) run_any=true; run_all=false ;; + --fix) auto_fix=true ;; + --help|-h) + echo "用法: $0 [--tsc] [--lint] [--build] [--any] [--fix] [--help]" + exit 0 + ;; + esac + done + + # 自动修复模式 + if [ "$auto_fix" = true ]; then + print_section "自动修复模式" + echo -e " 运行 eslint --fix ..." + npx eslint "{src,libs,test}/**/*.ts" --fix 2>/dev/null || true + echo -e " ${GREEN}自动修复完成${NC}" + echo "" + fi + + # 执行检查 + if [ "$run_all" = true ] || [ "$run_tsc" = true ]; then + check_tsc + fi + + if [ "$run_all" = true ] || [ "$run_lint" = true ]; then + check_eslint + fi + + if [ "$run_all" = true ] || [ "$run_build" = true ]; then + check_build + fi + + if [ "$run_all" = true ] || [ "$run_any" = true ]; then + check_any + fi + + # ---- 总结报告 ---- + echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}" + echo -e "${BLUE}${BOLD} 质量门禁报告${NC}" + echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}" + echo "" + + for result in "${RESULTS[@]}"; do + echo -e " $result" + done + echo "" + + local score_pct=0 + if [ "$TOTAL_CHECKS" -gt 0 ]; then + score_pct=$((TOTAL_SCORE * 100 / TOTAL_CHECKS)) + fi + + echo -e " ${BOLD}通过率: ${score_pct}% (${TOTAL_SCORE}/${TOTAL_CHECKS})${NC}" + echo "" + + if [ "$FAILED_CHECKS" -gt 0 ]; then + echo -e " ${RED}${BOLD}🚫 质量门禁未通过!${NC}${RED} (${FAILED_CHECKS} 项检查失败)${NC}" + echo -e " ${RED}请修复以上问题后重新提交。${NC}" + echo "" + exit 1 + else + echo -e " ${GREEN}${BOLD}✅ 质量门禁通过!${NC}${GREEN} 所有检查项均达标。${NC}" + echo "" + exit 0 + fi +} + +main "$@"