package com.tcm.training.admin.system.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.tcm.training.admin.common.PageResponse; import com.tcm.training.admin.system.dto.AppVersionCreateRequest; import com.tcm.training.admin.system.entity.AppVersionConfig; import com.tcm.training.admin.system.mapper.AppVersionConfigMapper; import com.tcm.training.admin.system.vo.AppVersionCheckVO; import com.tcm.training.admin.system.vo.AppVersionDetailVO; import com.tcm.training.admin.system.vo.AppVersionListItemVO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @Service public class AppVersionSettingsAppService { private final AppVersionConfigMapper appVersionConfigMapper; public AppVersionSettingsAppService(AppVersionConfigMapper appVersionConfigMapper) { this.appVersionConfigMapper = appVersionConfigMapper; } public PageResponse page(int page, int pageSize) { LambdaQueryWrapper qw = new LambdaQueryWrapper() .eq(AppVersionConfig::getDeleted, 0) .orderByDesc(AppVersionConfig::getVersionCode) .orderByDesc(AppVersionConfig::getId); IPage result = appVersionConfigMapper.selectPage(new Page(page, pageSize), qw); List list = new ArrayList(); for (AppVersionConfig item : result.getRecords()) { list.add(toListItem(item)); } return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize()); } public AppVersionDetailVO detail(Long id) { return toDetailVO(requireItem(id)); } @Transactional(rollbackFor = Exception.class) public AppVersionDetailVO create(AppVersionCreateRequest request) { AppVersionConfig latest = latest(); String androidFileUrl = normalizeOptionalText(request.getAndroidFileUrl()); String androidFileName = normalizeOptionalText(request.getAndroidFileName()); String iosFileUrl = normalizeOptionalText(request.getIosFileUrl()); String iosFileName = normalizeOptionalText(request.getIosFileName()); validatePackageFields(androidFileUrl, androidFileName, iosFileUrl, iosFileName); AppVersionConfig item = new AppVersionConfig(); item.setVersionNo(request.getVersionNo().trim()); item.setVersionCode(latest == null ? 1 : defaultNumber(latest.getVersionCode()) + 1); item.setAndroidFileUrl(androidFileUrl); item.setAndroidFileName(androidFileName); item.setIosFileUrl(iosFileUrl); item.setIosFileName(iosFileName); item.setForceUpdate(booleanToInteger(request.getForceUpdate())); item.setUpdateNotice(request.getUpdateNotice().trim()); item.setDeleted(0); appVersionConfigMapper.insert(item); return toDetailVO(item); } public AppVersionCheckVO checkUpdate(String platform, Integer currentVersionCode) { return checkUpdate(platform, currentVersionCode, null); } public AppVersionCheckVO checkUpdate(String platform, Integer currentVersionCode, String currentVersionNo) { if (currentVersionCode == null) { throw new IllegalArgumentException("当前版本号不能为空"); } if (currentVersionCode < 0) { throw new IllegalArgumentException("当前版本号不能小于0"); } String normalizedPlatform = normalizePlatform(platform); AppVersionConfig latest = latest(); AppVersionCheckVO vo = new AppVersionCheckVO(); if (latest == null) { vo.setHasUpdate(Boolean.FALSE); return vo; } String downloadUrl = AppVersionConfig.PLATFORM_ANDROID.equals(normalizedPlatform) ? normalizeOptionalText(latest.getAndroidFileUrl()) : normalizeOptionalText(latest.getIosFileUrl()); boolean hasUpdate = downloadUrl != null && isNewerVersion(latest, currentVersionCode, currentVersionNo); vo.setHasUpdate(hasUpdate); if (!hasUpdate) { return vo; } vo.setLatestVersionNo(latest.getVersionNo()); vo.setLatestVersionCode(defaultNumber(latest.getVersionCode())); vo.setForceUpdate(integerToBoolean(latest.getForceUpdate())); vo.setUpdateNotice(latest.getUpdateNotice()); vo.setDownloadUrl(downloadUrl); return vo; } public AppVersionCheckVO checkUpdateNew(String platform) { String normalizedPlatform = normalizePlatform(platform); AppVersionConfig latest = latest(); AppVersionCheckVO vo = new AppVersionCheckVO(); if (latest == null) { vo.setHasUpdate(Boolean.FALSE); return vo; } String downloadUrl = AppVersionConfig.PLATFORM_ANDROID.equals(normalizedPlatform) ? normalizeOptionalText(latest.getAndroidFileUrl()) : normalizeOptionalText(latest.getIosFileUrl()); boolean hasUpdate = downloadUrl != null; vo.setHasUpdate(hasUpdate); if (!hasUpdate) { return vo; } vo.setLatestVersionNo(latest.getVersionNo()); vo.setLatestVersionCode(defaultNumber(latest.getVersionCode())); vo.setForceUpdate(integerToBoolean(latest.getForceUpdate())); vo.setUpdateNotice(latest.getUpdateNotice()); vo.setDownloadUrl(downloadUrl); return vo; } /** * 判断后台最新版本是否高于当前 APP,兼容原生 versionCode 与展示 versionNo 不一致的历史包。 */ private boolean isNewerVersion(AppVersionConfig latest, Integer currentVersionCode, String currentVersionNo) { if (defaultNumber(latest.getVersionCode()) > currentVersionCode) { return true; } return compareVersionNo(latest.getVersionNo(), currentVersionNo) > 0; } /** * 比较数字点号版本号,无法解析的版本号不参与兜底判断。 */ private int compareVersionNo(String latestVersionNo, String currentVersionNo) { String latest = normalizeOptionalText(latestVersionNo); String current = normalizeOptionalText(currentVersionNo); if (latest == null || current == null) { return 0; } String[] latestParts = latest.split("\\."); String[] currentParts = current.split("\\."); int maxLength = Math.max(latestParts.length, currentParts.length); for (int i = 0; i < maxLength; i++) { Integer latestNumber = parseVersionNumber(latestParts, i); Integer currentNumber = parseVersionNumber(currentParts, i); if (latestNumber == null || currentNumber == null) { return 0; } int compareResult = latestNumber.compareTo(currentNumber); if (compareResult != 0) { return compareResult; } } return 0; } /** * 解析指定版本号片段,缺失片段按 0 处理。 */ private Integer parseVersionNumber(String[] parts, int index) { if (index >= parts.length) { return 0; } String value = parts[index] == null ? "" : parts[index].trim(); if (!value.matches("\\d+")) { return null; } try { return Integer.valueOf(value); } catch (NumberFormatException ex) { return null; } } private AppVersionConfig latest() { return appVersionConfigMapper.selectOne(new LambdaQueryWrapper() .eq(AppVersionConfig::getDeleted, 0) .orderByDesc(AppVersionConfig::getVersionCode) .orderByDesc(AppVersionConfig::getId) .last("limit 1")); } private AppVersionConfig requireItem(Long id) { AppVersionConfig item = appVersionConfigMapper.selectOne(new LambdaQueryWrapper() .eq(AppVersionConfig::getId, id) .eq(AppVersionConfig::getDeleted, 0) .last("limit 1")); if (item == null) { throw new IllegalArgumentException("APP版本不存在"); } return item; } private String normalizePlatform(String platform) { if (platform == null) { throw new IllegalArgumentException("平台类型不能为空"); } String normalized = platform.trim().toUpperCase(); if (AppVersionConfig.PLATFORM_ANDROID.equals(normalized) || AppVersionConfig.PLATFORM_IOS.equals(normalized)) { return normalized; } throw new IllegalArgumentException("平台类型不合法"); } private AppVersionListItemVO toListItem(AppVersionConfig item) { AppVersionListItemVO vo = new AppVersionListItemVO(); vo.setId(item.getId()); vo.setVersionNo(item.getVersionNo()); vo.setUpdateNotice(item.getUpdateNotice()); vo.setCreatedAt(item.getCreatedAt()); return vo; } private AppVersionDetailVO toDetailVO(AppVersionConfig item) { AppVersionDetailVO vo = new AppVersionDetailVO(); vo.setId(item.getId()); vo.setVersionNo(item.getVersionNo()); vo.setVersionCode(defaultNumber(item.getVersionCode())); vo.setAndroidFileUrl(item.getAndroidFileUrl()); vo.setAndroidFileName(item.getAndroidFileName()); vo.setIosFileUrl(item.getIosFileUrl()); vo.setIosFileName(item.getIosFileName()); vo.setForceUpdate(integerToBoolean(item.getForceUpdate())); vo.setUpdateNotice(item.getUpdateNotice()); vo.setCreatedAt(item.getCreatedAt()); return vo; } private Integer defaultNumber(Integer value) { return value == null ? 0 : value; } private Integer booleanToInteger(Boolean value) { return Boolean.TRUE.equals(value) ? 1 : 0; } private Boolean integerToBoolean(Integer value) { return value != null && value == 1; } private void validatePackageFields(String androidFileUrl, String androidFileName, String iosFileUrl, String iosFileName) { boolean hasAndroidFileUrl = androidFileUrl != null; boolean hasAndroidFileName = androidFileName != null; boolean hasIosFileUrl = iosFileUrl != null; boolean hasIosFileName = iosFileName != null; boolean hasAndroidPackage = hasAndroidFileUrl || hasAndroidFileName; boolean hasIosPackage = hasIosFileUrl || hasIosFileName; if (hasAndroidPackage && (!hasAndroidFileUrl || !hasAndroidFileName)) { throw new IllegalArgumentException("安卓文件地址和名称必须同时填写"); } if (hasIosPackage && (!hasIosFileUrl || !hasIosFileName)) { throw new IllegalArgumentException("iOS文件地址和名称必须同时填写"); } if (!hasAndroidPackage && !hasIosPackage) { throw new IllegalArgumentException("请至少上传一个安装包"); } } private String normalizeOptionalText(String value) { if (value == null) { return null; } String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } }