fix(mining-admin): 适配 mining-app 版本检查 API 格式

- CheckUpdateDto: current_version_code 改为 versionCode 匹配 mining-app 请求参数
- MobileVersionController: 响应格式改为 { hasUpdate, latestVersion: {...} } 匹配 mining-app 解析
- TransformInterceptor: 添加 @SkipTransform() 装饰器,移动端版本检查接口跳过响应包装

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-29 13:26:13 -08:00
parent f595c6f26d
commit 96e1fa4534
3 changed files with 40 additions and 21 deletions

View File

@ -2,12 +2,14 @@ import { Controller, Get, Query } from '@nestjs/common'
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger' import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'
import { VersionService } from '../../application/services/version.service' import { VersionService } from '../../application/services/version.service'
import { Platform } from '../../domain/version-management' import { Platform } from '../../domain/version-management'
import { CheckUpdateDto, UpdateCheckResultDto } from '../dto/version' import { CheckUpdateDto } from '../dto/version'
import { Public } from '../../shared/guards/admin-auth.guard' import { Public } from '../../shared/guards/admin-auth.guard'
import { SkipTransform } from '../../shared/interceptors/transform.interceptor'
/** /**
* Mobile App Version API Controller * Mobile App Version API Controller
* This endpoint is designed to match the mobile app's expected API format * Response format matches mining-app (Flutter) expected format:
* { hasUpdate: boolean, latestVersion?: { versionCode, versionName, ... } }
*/ */
@ApiTags('Mobile App Version') @ApiTags('Mobile App Version')
@Controller('api/app/version') @Controller('api/app/version')
@ -22,29 +24,31 @@ export class MobileVersionController {
@Get('check') @Get('check')
@Public() @Public()
@ApiOperation({ summary: '检查更新 (移动端专用)' }) @SkipTransform()
@ApiResponse({ status: 200, type: UpdateCheckResultDto }) @ApiOperation({ summary: '检查更新 (mining-app 专用)' })
async checkUpdate(@Query() dto: CheckUpdateDto): Promise<UpdateCheckResultDto> { async checkUpdate(@Query() dto: CheckUpdateDto) {
const platform = this.getPlatform(dto.platform) const platform = this.getPlatform(dto.platform)
const result = await this.versionService.checkUpdate(platform, dto.current_version_code) const result = await this.versionService.checkUpdate(platform, dto.versionCode)
if (!result.hasUpdate || !result.latestVersion) { if (!result.hasUpdate || !result.latestVersion) {
return { return { hasUpdate: false }
needUpdate: false,
}
} }
const v = result.latestVersion
return { return {
needUpdate: true, hasUpdate: true,
version: result.latestVersion.versionName, latestVersion: {
versionCode: result.latestVersion.versionCode, versionCode: v.versionCode,
downloadUrl: result.latestVersion.downloadUrl, versionName: v.versionName,
fileSize: Number(BigInt(result.latestVersion.fileSize)), buildNumber: v.buildNumber,
fileSizeFriendly: result.latestVersion.fileSizeFriendly, downloadUrl: v.downloadUrl,
sha256: result.latestVersion.fileSha256, fileSize: Number(BigInt(v.fileSize)),
forceUpdate: result.isForceUpdate, fileSha256: v.fileSha256,
updateLog: result.latestVersion.changelog, changelog: v.changelog,
releaseDate: result.latestVersion.releaseDate?.toISOString() ?? new Date().toISOString(), isForceUpdate: result.isForceUpdate,
minOsVersion: v.minOsVersion ?? null,
releaseDate: v.releaseDate?.toISOString() ?? null,
},
} }
} }
} }

View File

@ -17,5 +17,5 @@ export class CheckUpdateDto {
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(0)
current_version_code: number versionCode: number
} }

View File

@ -1,10 +1,25 @@
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Injectable, NestInterceptor, ExecutionContext, CallHandler, SetMetadata } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
export const SKIP_TRANSFORM_KEY = 'skipTransform';
export const SkipTransform = () => SetMetadata(SKIP_TRANSFORM_KEY, true);
@Injectable() @Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, any> { export class TransformInterceptor<T> implements NestInterceptor<T, any> {
constructor(private readonly reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const skip = this.reflector.getAllAndOverride<boolean>(SKIP_TRANSFORM_KEY, [
context.getHandler(),
context.getClass(),
]);
if (skip) {
return next.handle();
}
return next.handle().pipe(map((data) => ({ success: true, data, timestamp: new Date().toISOString() }))); return next.handle().pipe(map((data) => ({ success: true, data, timestamp: new Date().toISOString() })));
} }
} }