feat(version-service): add GET /api/app/version/check endpoint for Flutter app
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 <noreply@anthropic.com>
This commit is contained in:
parent
95d678ad6b
commit
141c6e984d
|
|
@ -47,4 +47,23 @@ export class AppVersionRepository {
|
||||||
async delete(id: string): Promise<void> {
|
async delete(id: string): Promise<void> {
|
||||||
await this.repo.delete(id);
|
await this.repo.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询指定平台最新启用版本(buildNumber 最大的已启用版本) */
|
||||||
|
async findLatestEnabled(platform: Platform): Promise<AppVersion | null> {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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`;
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { DatabaseModule } from '@it0/database';
|
||||||
import { AppVersion } from './domain/entities/app-version.entity';
|
import { AppVersion } from './domain/entities/app-version.entity';
|
||||||
import { AppVersionRepository } from './infrastructure/repositories/app-version.repository';
|
import { AppVersionRepository } from './infrastructure/repositories/app-version.repository';
|
||||||
import { VersionController } from './interfaces/rest/controllers/version.controller';
|
import { VersionController } from './interfaces/rest/controllers/version.controller';
|
||||||
|
import { AppVersionCheckController } from './interfaces/rest/controllers/app-version-check.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -12,7 +13,7 @@ import { VersionController } from './interfaces/rest/controllers/version.control
|
||||||
DatabaseModule.forRoot(),
|
DatabaseModule.forRoot(),
|
||||||
TypeOrmModule.forFeature([AppVersion]),
|
TypeOrmModule.forFeature([AppVersion]),
|
||||||
],
|
],
|
||||||
controllers: [VersionController],
|
controllers: [VersionController, AppVersionCheckController],
|
||||||
providers: [AppVersionRepository],
|
providers: [AppVersionRepository],
|
||||||
})
|
})
|
||||||
export class VersionModule {}
|
export class VersionModule {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue