简单描述一下总的过程:在某个后台上(版本发布平台)上传原始的ipa文件,解析ipa(主要是解析info.plist,从中获取软件名、版本、icons等;解析embedded.mobileprovision,获取证书过期时间),生成一个新的plist文件,最终将ipa、新的plist、图标上传至发布服务器。这个plist文件里面会指向这个ipa的地址,最终在Safari上访问 itms-services://?action=download-manifest&url=plist文件 就可以下载IOS应用了。
下面先来看看最重要的plist文件.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://uc-download.xxx.com/api/v1/file/download/5194c4cfcb3d4c128c51b9e3138ca8e8</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>https://uc-download.xxx.com/api/v1/file/download/d11184a8a9184c00836f86e14ed11d10</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>https://uc-download.xxx.com/api/v1/file/download/d11184a8a9184c00836f86e14ed11d10</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>enterprise.xxx.Odin-UC</string>
<key>bundle-version</key>
<string>2.0.0.66</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>UME</string>
</dict>
</dict>
</array>
</dict>
</plist>
一、服务器
首先,我们需要一台服务器,并且有SSL证书。这是因为最终在Safari上访问plist文件需要。
二、解析IPA
FileService这里主要用到就是在本地构造临时文件,ClientVersionRecord是数据库存储的版本实体对象,比较简单,不再赘述。
controller入口可以见:https://blog.csdn.net/Mr_EvanChen/article/details/106937688
/**
* 解析ios安装包,后缀名为ipa
*/
@Service
public class IOSPacketParser extends InstallPacketParser {
private static final Logger logger = LoggerFactory.getLogger(IOSPacketParser.class);
@Autowired
private FileService fileService;
@Autowired
private RestTemplate restTemplate;
@Autowired
private IosIntranetService iosIntranetService;
@Override
public ClientType getClientType() {
return ClientType.IOS;
}
@Override
public ClientVersionRecord parse(File ipaFile, ClientVersionRecord version) throws Exception {
String intranetIpaUrl = version.getIpaDownloadUrl();
String intranetUrl = intranetIpaUrl.substring(0, intranetIpaUrl.lastIndexOf("/") + 1);
IPA ipa = iosIntranetService.transFile2IPA(ipaFile);
String ipaFileName = ipaFile.getName();
ipaFileName = ipaFileName.substring(0, ipaFileName.lastIndexOf("."));
//生成icon
String iconUrl = buildIcon(ipaFile, ipa, intranetUrl + ipaFileName);
//生成plist
File plistFile = iosIntranetService.createPlistFile(ipa, intranetIpaUrl, iconUrl, ipaFileName);
String plistUrl = iosIntranetService.uploadPlistAsFile(plistFile, intranetUrl + plistFile.getName());
version.setMd5(FileUtil.md5(ipaFile))
.setFileSize(ipaFile.length())
.setClientVersion(ipa.getCFBundleVersion())
.setIconUrl(iconUrl)
.setIntranetDownloadUrl("itms-services://?action=download-manifest&url=" + plistUrl);
logger.info("parse [ipa] file, version: {}", JSONUtil.serialize(version));
return version;
}
//----------------------------------private-----------------------------------------
private String buildIcon(File ipaFile, IPA ipa, String intranetIconUrl) {
String iconName = getMaxIcon(ipa);
File oriIcon = null;
File tmpDecodedIcon = null;
try {
oriIcon = readIcons(ipaFile, iconName);
tmpDecodedIcon = new File(fileService.getTempDir() + "decoded-" + oriIcon.getName());
new IPngConverter(oriIcon, tmpDecodedIcon).convert();
String oriIconName = oriIcon.getName();
intranetIconUrl = intranetIconUrl + StringPool.DOT + oriIconName.substring(oriIconName.lastIndexOf(StringPool.DOT) + 1);
restTemplate.put(intranetIconUrl, new FileSystemResource(tmpDecodedIcon));
} catch (Exception e) {
logger.info("上传icon至内网失败,结果:{}", intranetIconUrl);
} finally {
if (oriIcon != null) {
logger.info("删除临时文件,结果:{}", oriIcon.delete());
logger.info("删除临时文件,结果:{}", oriIcon.getParentFile().delete());
}
if (tmpDecodedIcon != null) {
logger.info("删除临时文件,结果:{}",tmpDecodedIcon.delete());
logger.info("删除临时文件,结果:{}",tmpDecodedIcon.getParentFile().delete());
}
}
return intranetIconUrl;
}
private File readIcons(F