From 141c6e984d3295aa1b2f0ecd9a2eb894234b4dd1 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 6 Mar 2026 05:24:04 -0800 Subject: [PATCH] feat(version-service): add GET /api/app/version/check endpoint for Flutter app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flutter VersionChecker was calling GET /api/app/version/check but this endpoint didn't exist — only the admin CRUD /api/v1/versions was there. New: AppVersionCheckController (@Controller('api/app/version')) GET /api/app/version/check?platform=android¤t_version_code=N - Finds latest enabled version for the platform (highest buildNumber) - Returns { needUpdate: false } when already up to date - Returns full VersionInfo payload when update is available Response fields match Flutter VersionInfo.fromJson exactly: needUpdate, version, versionCode, downloadUrl, fileSize, fileSizeFriendly (computed), sha256 (empty — not stored), forceUpdate, updateLog, releaseDate Also: AppVersionRepository.findLatestEnabled(platform) — queries all enabled versions for platform, picks the one with the highest buildNumber (parsed as int, robust against varchar storage). Co-Authored-By: Claude Sonnet 4.6 --- .../repositories/app-version.repository.ts | 19 +++++ .../app-version-check.controller.ts | 70 +++++++++++++++++++ .../version-service/src/version.module.ts | 3 +- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 packages/services/version-service/src/interfaces/rest/controllers/app-version-check.controller.ts diff --git a/packages/services/version-service/src/infrastructure/repositories/app-version.repository.ts b/packages/services/version-service/src/infrastructure/repositories/app-version.repository.ts index b588ab3..bd2b6b3 100644 --- a/packages/services/version-service/src/infrastructure/repositories/app-version.repository.ts +++ b/packages/services/version-service/src/infrastructure/repositories/app-version.repository.ts @@ -47,4 +47,23 @@ export class AppVersionRepository { async delete(id: string): Promise { await this.repo.delete(id); } + + /** 查询指定平台最新启用版本(buildNumber 最大的已启用版本) */ + async findLatestEnabled(platform: Platform): Promise { + const versions = await this.repo + .createQueryBuilder('v') + .where('v.platform = :platform', { platform }) + .andWhere('v.isEnabled = true') + .orderBy('v.createdAt', 'DESC') + .getMany(); + + if (versions.length === 0) return null; + + // 找 buildNumber(整数)最大的版本,兼容字符串存储 + return versions.reduce((best, cur) => { + const bestCode = parseInt(best.buildNumber, 10) || 0; + const curCode = parseInt(cur.buildNumber, 10) || 0; + return curCode > bestCode ? cur : best; + }); + } } diff --git a/packages/services/version-service/src/interfaces/rest/controllers/app-version-check.controller.ts b/packages/services/version-service/src/interfaces/rest/controllers/app-version-check.controller.ts new file mode 100644 index 0000000..13d0119 --- /dev/null +++ b/packages/services/version-service/src/interfaces/rest/controllers/app-version-check.controller.ts @@ -0,0 +1,70 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { AppVersionRepository } from '../../../infrastructure/repositories/app-version.repository'; +import { Platform } from '../../../domain/entities/app-version.entity'; + +/** + * 公开接口:供 IT0 App 检查是否有新版本 + * GET /api/app/version/check + * + * 查询参数: + * platform - 平台 (android | ios),不区分大小写 + * current_version_code - 当前安装的构建号 (build number) + * + * 响应格式(与 Flutter VersionInfo.fromJson 完全对应): + * needUpdate bool - 是否需要更新 + * version string - 最新版本号 (versionName, e.g. "1.0.0") + * versionCode int - 最新构建号 (buildNumber as int) + * downloadUrl string - APK 下载地址 + * fileSize int - 文件大小(字节) + * fileSizeFriendly string - 友好显示 (e.g. "146.9 MB") + * sha256 string - SHA-256(暂未存储,返回空串) + * forceUpdate bool - 是否强制更新 + * updateLog string? - 更新日志 + * releaseDate string - 发布时间 ISO 字符串 + */ +@Controller('api/app/version') +export class AppVersionCheckController { + constructor(private readonly versionRepo: AppVersionRepository) {} + + @Get('check') + async check( + @Query('platform') platform: string, + @Query('current_version_code') currentVersionCode?: string, + ) { + const platformEnum = ((platform || 'android').toUpperCase()) as Platform; + const latest = await this.versionRepo.findLatestEnabled(platformEnum); + + if (!latest) { + return { needUpdate: false }; + } + + const latestCode = parseInt(latest.buildNumber, 10) || 0; + const currentCode = parseInt(currentVersionCode || '0', 10) || 0; + + if (latestCode <= currentCode) { + return { needUpdate: false }; + } + + const fileSizeNum = Number(latest.fileSize) || 0; + + return { + needUpdate: true, + version: latest.versionName, + versionCode: latestCode, + downloadUrl: latest.downloadUrl || '', + fileSize: fileSizeNum, + fileSizeFriendly: formatFileSize(fileSizeNum), + sha256: '', // version-service 不存储 sha256,留空 + forceUpdate: latest.isForceUpdate, + updateLog: latest.changelog ?? null, + releaseDate: (latest.releaseDate ?? latest.createdAt).toISOString(), + }; + } +} + +function formatFileSize(bytes: number): string { + if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`; + if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${bytes} B`; +} diff --git a/packages/services/version-service/src/version.module.ts b/packages/services/version-service/src/version.module.ts index 386744f..7197f2c 100644 --- a/packages/services/version-service/src/version.module.ts +++ b/packages/services/version-service/src/version.module.ts @@ -5,6 +5,7 @@ import { DatabaseModule } from '@it0/database'; import { AppVersion } from './domain/entities/app-version.entity'; import { AppVersionRepository } from './infrastructure/repositories/app-version.repository'; import { VersionController } from './interfaces/rest/controllers/version.controller'; +import { AppVersionCheckController } from './interfaces/rest/controllers/app-version-check.controller'; @Module({ imports: [ @@ -12,7 +13,7 @@ import { VersionController } from './interfaces/rest/controllers/version.control DatabaseModule.forRoot(), TypeOrmModule.forFeature([AppVersion]), ], - controllers: [VersionController], + controllers: [VersionController, AppVersionCheckController], providers: [AppVersionRepository], }) export class VersionModule {}