fix: 全服务注册 JwtStrategy + 修复微信 WxApi 未配置 crash

**后端**
- packages/common: 新增并导出 JwtStrategy(共享 Passport JWT 策略)
- 6 个服务模块(user/issuer/clearing/compliance/notification/telemetry)
  均缺少 JwtStrategy provider,导致所有受保护接口返回 500
  "Unknown authentication strategy jwt"
- 统一修复:各模块 providers 添加 JwtStrategy

**Flutter**
- welcome_page: _onWechatTap() 的 isWeChatInstalled 调用未设 WECHAT_APP_ID
  时会抛出 PlatformException,catch 后降级为"未安装"提示

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-04 22:24:07 -08:00
parent 6bdd8d1e19
commit 75ed31cc04
9 changed files with 46 additions and 1 deletions

View File

@ -8,6 +8,9 @@ export * from './decorators/roles.decorator';
export * from './guards/jwt-auth.guard'; export * from './guards/jwt-auth.guard';
export * from './guards/roles.guard'; export * from './guards/roles.guard';
// Strategies
export * from './strategies/jwt.strategy';
// Interceptors // Interceptors
export * from './interceptors/logging.interceptor'; export * from './interceptors/logging.interceptor';
export * from './interceptors/transform.interceptor'; export * from './interceptors/transform.interceptor';

View File

@ -0,0 +1,25 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
/**
* Shared JWT Passport Strategy.
* Register as a provider in each service module that uses JwtAuthGuard.
*
* Usage in module:
* providers: [JwtStrategy, ...]
*/
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_ACCESS_SECRET || 'dev-access-secret',
});
}
async validate(payload: { sub: string; role: string; kycLevel: number; type: string }) {
return { sub: payload.sub, role: payload.role, kycLevel: payload.kycLevel };
}
}

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from '@genex/common';
// Domain entities // Domain entities
import { Settlement } from './domain/entities/settlement.entity'; import { Settlement } from './domain/entities/settlement.entity';
@ -44,6 +45,7 @@ import { AdminReportsController } from './interface/http/controllers/admin-repor
], ],
controllers: [ClearingController, AdminFinanceController, AdminReportsController], controllers: [ClearingController, AdminFinanceController, AdminReportsController],
providers: [ providers: [
JwtStrategy,
// Repository DI bindings (interface -> implementation) // Repository DI bindings (interface -> implementation)
{ provide: SETTLEMENT_REPOSITORY, useClass: SettlementRepository }, { provide: SETTLEMENT_REPOSITORY, useClass: SettlementRepository },
{ provide: REFUND_REPOSITORY, useClass: RefundRepository }, { provide: REFUND_REPOSITORY, useClass: RefundRepository },

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from '@genex/common';
// ─── Domain Entities ─── // ─── Domain Entities ───
import { AmlAlert } from './domain/entities/aml-alert.entity'; import { AmlAlert } from './domain/entities/aml-alert.entity';
@ -75,6 +76,7 @@ import { AdminInsuranceController } from './interface/http/controllers/admin-ins
AdminInsuranceController, AdminInsuranceController,
], ],
providers: [ providers: [
JwtStrategy,
// ─── Repository DI bindings (interface → implementation) ─── // ─── Repository DI bindings (interface → implementation) ───
{ provide: AML_ALERT_REPOSITORY, useClass: AmlAlertRepository }, { provide: AML_ALERT_REPOSITORY, useClass: AmlAlertRepository },
{ provide: OFAC_SCREENING_REPOSITORY, useClass: OfacScreeningRepository }, { provide: OFAC_SCREENING_REPOSITORY, useClass: OfacScreeningRepository },

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from '@genex/common';
// Domain entities // Domain entities
import { Issuer } from './domain/entities/issuer.entity'; import { Issuer } from './domain/entities/issuer.entity';
@ -88,6 +89,7 @@ import { RolesGuard } from './interface/http/guards/roles.guard';
CouponBatchController, CouponBatchController,
], ],
providers: [ providers: [
JwtStrategy,
// Infrastructure -> Domain port binding (Repository pattern) // Infrastructure -> Domain port binding (Repository pattern)
{ provide: ISSUER_REPOSITORY, useClass: IssuerRepository }, { provide: ISSUER_REPOSITORY, useClass: IssuerRepository },
{ provide: COUPON_REPOSITORY, useClass: CouponRepository }, { provide: COUPON_REPOSITORY, useClass: CouponRepository },

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from '@genex/common';
// Domain entities // Domain entities
import { Notification } from './domain/entities/notification.entity'; import { Notification } from './domain/entities/notification.entity';
@ -94,6 +95,7 @@ import { DeviceTokenController } from './interface/http/controllers/device-token
DeviceTokenController, DeviceTokenController,
], ],
providers: [ providers: [
JwtStrategy,
// Infrastructure -> Domain repository binding // Infrastructure -> Domain repository binding
{ provide: NOTIFICATION_REPOSITORY, useClass: NotificationRepository }, { provide: NOTIFICATION_REPOSITORY, useClass: NotificationRepository },
{ provide: ANNOUNCEMENT_REPOSITORY, useClass: AnnouncementRepositoryImpl }, { provide: ANNOUNCEMENT_REPOSITORY, useClass: AnnouncementRepositoryImpl },

View File

@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleModule } from '@nestjs/schedule';
import { JwtStrategy } from '@genex/common';
// Domain entities // Domain entities
import { TelemetryEvent } from './domain/entities/telemetry-event.entity'; import { TelemetryEvent } from './domain/entities/telemetry-event.entity';
@ -72,6 +73,7 @@ import { HealthController } from './interface/http/controllers/health.controller
HealthController, HealthController,
], ],
providers: [ providers: [
JwtStrategy,
{ provide: TELEMETRY_EVENT_REPOSITORY, useClass: TelemetryEventRepository }, { provide: TELEMETRY_EVENT_REPOSITORY, useClass: TelemetryEventRepository },
{ provide: ONLINE_SNAPSHOT_REPOSITORY, useClass: OnlineSnapshotRepository }, { provide: ONLINE_SNAPSHOT_REPOSITORY, useClass: OnlineSnapshotRepository },
{ provide: DAILY_ACTIVE_STATS_REPOSITORY, useClass: DailyActiveStatsRepository }, { provide: DAILY_ACTIVE_STATS_REPOSITORY, useClass: DailyActiveStatsRepository },

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from '@genex/common';
// Domain entities // Domain entities
import { User } from './domain/entities/user.entity'; import { User } from './domain/entities/user.entity';
@ -59,6 +60,7 @@ import { AdminAnalyticsController } from './interface/http/controllers/admin-ana
AdminDashboardController, AdminUserController, AdminSystemController, AdminAnalyticsController, AdminDashboardController, AdminUserController, AdminSystemController, AdminAnalyticsController,
], ],
providers: [ providers: [
JwtStrategy,
// Infrastructure -> Domain port binding // Infrastructure -> Domain port binding
{ provide: USER_REPOSITORY, useClass: UserRepository }, { provide: USER_REPOSITORY, useClass: UserRepository },
{ provide: KYC_REPOSITORY, useClass: KycRepository }, { provide: KYC_REPOSITORY, useClass: KycRepository },

View File

@ -78,7 +78,12 @@ class _WelcomePageState extends ConsumerState<WelcomePage> {
// //
Future<void> _onWechatTap() async { Future<void> _onWechatTap() async {
final installed = await _fluwx.isWeChatInstalled; bool installed = false;
try {
installed = await _fluwx.isWeChatInstalled;
} catch (_) {
// WxApi not configured (WECHAT_APP_ID not provided) treat as not installed
}
if (!installed) { if (!installed) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(