You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

542 lines
18 KiB
PHP

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace Base\Service;
use ZipArchive;
use Base\Model\PromoteModel;
use Base\Model\ApplyModel;
use Base\Tool\Base62;
use Base\Tool\PlistDemo;
use Base\Tool\Storage;
use GuzzleHttp\Client;
class GameSourceService {
const IS_FIXED_IOS13 = false;
const DEBUG = true;
public function __construct()
{
}
/**
* 获取IPA中的.app后缀目录
*/
public function getIpaAppPath($zip)
{
$appPath = '';
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if (preg_match("/^Payload.*?\.app/", $name, $matches)) {
$appPath = $matches[0];
break;
}
}
return $appPath;
}
public function deleteOldChannelConfigFolder($zip, $txChannelFolder)
{
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if (strpos($name, $txChannelFolder) === 0) {
$zip->deleteName($name);
}
}
}
public function getChannelConfigFile($zip, $sdkVersion)
{
$configUrl = '';
if ($sdkVersion == 1) {
$configUrl = "META-INF/mch.properties";
} else {
$appPath = $this->getIpaAppPath($zip);
$configUrl = $appPath . '/_CodeSignature/TXChannel';
}
return $configUrl;
}
public function getChannelConfigFolder($zip, array $packData)
{
$appPath = $this->getIpaAppPath($zip);
return $appPath . '/TXChannel/' . Base62::encode(json_encode($packData, JSON_UNESCAPED_UNICODE));
}
public function getGameSourceUrl($gameSource)
{
if (empty($gameSource)) {
return '';
}
$path = '';
if($gameSource['file_type'] == 1) {
$path = './Uploads/SourcePack/';
}else{
$path = './Uploads/Ios/original/';
}
$fileUrl = $path . $gameSource['file_name'];
return ROOTTT . ltrim($fileUrl, './');
}
public function packChannel($localPath, $game, $gameSource, $apply = null)
{
$packData = [
'game_id' => $game['id'],
'game_name' => $game['game_name'],
'game_appid' => $game['game_appid'],
'promote_id' => $apply ? $apply['promote_id'] : '0',
'promote_account' => $apply ? $apply['promote_account'] : '自然渠道',
'source_version' => $gameSource['source_version'],
];
$zip = new \ZipArchive();
if (!$zip->open($localPath, \ZipArchive::CREATE)) {
return false;
}
$isNewSdk = (isset($gameSource['is_new_sdk']) && $gameSource['is_new_sdk'] == 1);
if ($isNewSdk && $game['sdk_version'] == 2) {
$channelFolder = $this->getChannelConfigFolder($zip, $packData);
return $this->packChannelFolder($zip, $channelFolder);
} else {
$channelFile = $this->getChannelConfigFile($zip, $game['sdk_version']);
return $this->packChannelFile($zip, $channelFile, $packData);
}
$zip->close();
return false;
}
/**
* 打入渠道信息文件
* @param ZipArchive $zip 包文件
* @param string $distFile 打入文件名
* @param array $packData 打入信息
* @return boolean 是否成功
*/
public function packChannelFile($zip, $distFile, array $packData)
{
$zip->addFromString($distFile, json_encode($packData));
return true;
}
/**
* 打入渠道信息目录用于IOS主要为了解决IOS13问题
* @param ZipArchive $zip 包文件
* @param string $distFolder 打入文件夹
* @return boolean 是否成功
*/
public function packChannelFolder($zip, $distFolder)
{
$rows = explode('/' , $distFolder);
unset($rows[count($rows) -1]);
$txChannelFolder = implode('/', $rows) . '/';
$this->deleteOldChannelConfigFolder($zip, $txChannelFolder);
$zip->addEmptyDir($distFolder);
return true;
}
/**
* 原包打包
*/
public function sourcePack($gameSource, $game, $olderSource = null)
{
$relativeUrl = $gameSource['file_url'];
$localPath = ROOTTT . ltrim($relativeUrl, './');
$originalUrl = '';
if ($game['sdk_version'] == 2) {
$oldLocalPath = $localPath;
$localPath = str_replace('Uploads/SourcePack', 'Uploads/Ios/original', $oldLocalPath);
$originalUrl = $relativeUrl;
$relativeUrl = str_replace('Uploads/SourcePack', 'Uploads/Ios/original', $relativeUrl);
copy($oldLocalPath, $localPath);
}
$status = $this->packChannel($localPath, $game, $gameSource);
if (!$status) {
return [
'status' => false,
'message' => '打包失败,原包加入信息失败!',
];
}
if ($olderSource) {
$this->removeOlderFile($olderSource['plist_url']);
$this->removeOlderFile($olderSource['org_plist_url']);
$this->removeOlderFile($olderSource['file_url']);
$this->removeOlderFile($olderSource['original_url']);
}
$distFilePath = 'SourcePack/' . $gameSource['file_name'];
$result = $this->uploadPackage($localPath, $distFilePath);
$fileUrl = '';
if ($result['status']) {
$fileUrl = $result['data']['url'];
$fileUrl = $fileUrl == '@' ? $relativeUrl : $fileUrl;
} else {
return [
'status' => false,
'message' => '打包失败,上传文件失败!' . $result['message'],
];
}
$plistUrl = '';
$orgPlistUrl = '';
if ($game['sdk_version'] == 2) {
$params = [
'packageName' => $gameSource['bao_name'],
'gameId' => $game['id'],
'gameName' => $game['game_name'],
'promoteId' => 0,
'packageUrl' => $fileUrl,
'gameIcon' => get_cover($game['icon'], 'path'),
'type' => 'pack'
];
$result = $this->createPlist($params);
if ($result['status']) {
$plistUrl = $result['data']['path'];
} else {
return [
'status' => false,
'message' => '打包失败生成plist文件失败' . $result['message'],
];
}
$params['packageUrl'] = C('DOMAIN_DOWNLOAD') . ltrim($originalUrl, '.');
$params['type'] = 'org';
$result = $this->createPlist($params);
if ($result['status']) {
$orgPlistUrl = $result['data']['path'];
} else {
return [
'status' => false,
'message' => '打包失败生成原包plist文件失败' . $result['message'],
];
}
}
return [
'status' => true,
'message' => '打包成功!',
'data' => [
'plistUrl' => $plistUrl,
'orgPlistUrl' => $orgPlistUrl,
'fileUrl' => $fileUrl,
'originalUrl' => $originalUrl,
]
];
}
private function isRemoteFile(string $file)
{
if (strpos($file, 'http://') === 0 || strpos($file, 'https://') === 0) {
return true;
}
return false;
}
private function removeOlderFile(string $file)
{
if (!$file) {
return;
}
if ($this->isRemoteFile($file)) {
$urlInfo = parse_url($file);
$objectName = trim($urlInfo['path'], '/');
$this->deletePackage($objectName);
} else {
$olderPackage = ROOTTT . substr($file, 2);
if (file_exists($olderPackage)) {
unlink($olderPackage);
}
}
}
/**
* 渠道打包
*/
public function channelPack($gameSource, $game, $apply, $launchCount = 0)
{
$debugTime1 = microtime(true);
$gameSourceUrl = $this->getGameSourceUrl($gameSource);
if ($gameSource == null || !file_exists($gameSourceUrl)) {
M('apply', 'tab_')->where(['id' => $apply['id']])->setField('enable_status', -1);
return [
'status' => false,
'message' => '打包失败,原包或原包路径不存在!',
];
}
/* 检测是否存在投放申请,存在则更改投放申请信息,否则进行渠道打包 */
if ($launchCount > 0) {
M('apply', 'tab_')->where(['id' => $apply['id']])->setField('enable_status', 3);
$launchData = ['launch_packge' => 2, 'launch_down_url'=>'' , 'launch_plist_url'=>''];
M('apply_launch', 'tab_')->where(['apply_id' => $apply['id'], 'launch_packge'=>['in', [0, 2, 3]]])->save($launchData);
}
$this->removeOlderFile($apply['pack_url']);
$this->removeOlderFile($apply['plist_url']);
$savePath = '';
$fileName = $apply['game_id'] . '_' . substr(md5(microtime(true) . 'game_package' . $apply['game_id'] . '-' . $apply['promote_id']), 8 , 16);
if ($apply['sdk_version'] == 1) {
$fileName .= '.apk';
$savePath = 'Uploads/GamePack/' . $fileName;
} else {
$fileName .= '.ipa';
$savePath = 'Uploads/IosGamePack/' . $fileName;
}
$relativePath = './' . $savePath;
$localPath = ROOTTT . $savePath;
$debugTime2 = microtime(true);
copy($gameSourceUrl, $localPath);
$debugTime3 = microtime(true);
$status = $this->packChannel($localPath, $game, $gameSource, $apply);
if (!$status) {
return [
'status' => false,
'message' => '打包失败,加入渠道信息失败!',
];
}
$debugTime4 = microtime(true);
$distFilePath = 'GamePack/' . $fileName;
$result = $this->uploadPackage($localPath, $distFilePath, true);
if ($result['status']) {
$packageUrl = $result['data']['url'];
$packageUrl = $packageUrl == '@' ? $relativePath : $packageUrl;
} else {
return [
'status' => false,
'message' => '打包失败,上传文件失败!' . $result['message'],
];
}
$debugTime5 = microtime(true);
$plistUrl = '';
if ($apply['sdk_version'] == 2) {
$params = [
'packageName' => $gameSource['bao_name'],
'gameId' => $game['id'],
'gameName' => $game['game_name'],
'promoteId' => $apply['promote_id'],
'packageUrl' => $packageUrl,
'gameIcon' => get_cover($game['icon'], 'path'),
'type' => 'pack'
];
$result = $this->createPlist($params);
if ($result['status']) {
$plistUrl = $result['data']['path'];
} else {
return [
'status' => false,
'message' => '打包失败生成plist文件失败' . $result['message'],
];
}
}
$applyService = new ApplyService();
$applyService->updateAfterPack($apply, $packageUrl, $plistUrl);
$debugTime6 = microtime(true);
$debugInfo = '';
if (self::DEBUG) {
$debugInfo .= '[' .
'总计:' . ($debugTime6 - $debugTime1) .
' 复制:' . ($debugTime3 - $debugTime2) .
' 打包:' . ($debugTime4 - $debugTime3) .
' 上传:' . ($debugTime5 - $debugTime4) .
']';
}
return [
'status' => true,
'message' => '打包成功!' . $debugInfo,
'data' => [
'plistUrl' => $plistUrl,
]
];
}
public function uploadPackage($localFilePath, $distFilePath, $isDeleteLocal = false)
{
$isChunk = C('PACKAGE_CHUNK_ENABLED') ? true : false;
if (C('STORAGE_TYPE')) {
if ($isChunk) {
return $this->uploadPackageChunk($localFilePath, $distFilePath, $isDeleteLocal);
} else {
return $this->uploadPackageOnce($localFilePath, $distFilePath, $isDeleteLocal);
}
} else {
// 不上传OSS等第三方服务器
return [
'status' => true,
'message' => '上传成功',
'data' => [
'url' => '@'
],
];
}
}
public function deletePackage($objectName)
{
$client = new Client([
'base_uri' => C('PACKAGE_CHUNK_URL'),
'timeout' => 30.0,
]);
try {
$response = $client->post('/delete', [
'verify' => false,
'form_params' => [
'file' => $objectName,
]
]);
$result = (string)$response->getBody();
$result = json_decode($result, true);
if (!$result) {
return [
'status' => false,
'message' => '请求异常',
];
}
} catch (\Exception $e) {
return [
'status' => false,
'message' => '请求异常: ' . $e->getMessage(),
];
}
return $result;
}
public function uploadPackageChunk($localFilePath, $distFilePath, $isDeleteLocal = false)
{
$client = new Client([
'base_uri' => C('PACKAGE_CHUNK_URL'),
'timeout' => 30.0,
]);
try {
$response = $client->post('/upload/upload', [
'verify' => false,
'form_params' => [
'file' => $localFilePath,
'dest' => $distFilePath,
'is_delete_local' => ($isDeleteLocal ? 1 : 0),
]
]);
$result = (string)$response->getBody();
$result = json_decode($result, true);
if (!$result) {
return [
'status' => false,
'message' => '请求异常',
];
}
} catch (\Exception $e) {
return [
'status' => false,
'message' => '请求异常: ' . $e->getMessage(),
];
}
if ($result['code'] == '0000') {
$result['status'] = true;
}
return $result;
}
public function uploadPackageOnce($localFilePath, $distFilePath, $isDeleteLocal = false)
{
$result = Storage::upload($localFilePath, $distFilePath);
if ($isDeleteLocal && file_exists($localFilePath)) {
@unlink($localFilePath);
}
return $result;
}
//生成游戏渠道plist文件
public function createPlist($params)
{
$packageName = $params['packageName'] ?? '';
$gameName = $params['gameName'] ?? '';
$gameId = $params['gameId'] ?? 0;
$promoteId = $params['promoteId'] ?? 0;
$packageUrl = $params['packageUrl'] ?? '';
$platformId = $params['platformId'] ?? 0;
$position = $params['position'] ?? 0;
$gameIcon = $params['gameIcon'] ?? '';
$type = $params['type'] ?? '';
if ($gameId == 0) {
return [
'status' => false,
'message' => '游戏ID错误',
];
}
if ($type == '' || !in_array($type, ['org', 'pack'])) {
return [
'status' => false,
'message' => '类型错误',
];
}
$xml = new \DOMDocument();
$xml->loadXML(PlistDemo::$content);
$online = $xml->getElementsByTagName('dict'); //查找节点
$elements = $online->item(1)->getElementsByTagName('string'); //第二个节点下所有string
foreach ($elements as $element) {
switch ($element->textContent) {
case 'ipa_url':
if (preg_match("/http/", $packageUrl)) {
$element->nodeValue = $packageUrl;
} else {
$element->nodeValue = C('DOMAIN_DOWNLOAD') . ltrim($packageUrl, '.');
}
break;
case 'icon':
if (preg_match("/http/", $gameIcon)) {
$element->nodeValue = $gameIcon;
} else {
$element->nodeValue = C('DOMAIN_OFFICIAL_PC') . ltrim($gameIcon, '.');
}
break;
case 'com.dell':
$element->nodeValue = $packageName;
break;
case '1.0.0':
$element->nodeValue = false;
break;
case 'mchdemo':
$element->nodeValue = $gameName;
break;
}
}
$fileName = $gameId . '_' . substr(md5($type . '_' . $gameId . '_' . $promoteId . '_' . $platformId . '_' . $position . '_' . microtime()), 8, 16);
if ($type == 'pack') {
if ($promoteId == 0) {
$fileName = 'Uploads/SourcePlist/'. $fileName . '.Plist';
} else {
$fileName = 'Uploads/GamePlist/'. $fileName . '.Plist';
}
} elseif ($type == 'org') {
$fileName = 'Uploads/OrgSourcePlist/'. $fileName . '.Plist';
}
$xml->save('./' . $fileName);
return [
'status' => true,
'message' => '生成成功',
'data' => [
'path' => './' . $fileName
]
];
}
}