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 string $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 string $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) { $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' => '打包失败,原包加入信息失败!', ]; } $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' => '打包失败,上传OSS失败!' . $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, ] ]; } /** * 渠道打包 */ 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); } /* 检测是否存在投放申请,存在则更改投放申请信息,否则进行渠道打包 */ 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); } $savePath = ''; $fileName = 'game_package' . $apply['game_id'] . '-' . $apply['promote_id']; 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' => '打包失败,上传OSS失败!' . $result['message'], ]; } $debugTime5 = microtime(true); $plistInfo = ['game_id' => $apply['game_id'], 'promote_id' => $apply['promote_id']]; $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 (get_tool_status('oss_storage') == 1) { 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 uploadPackageChunk($localFilePath, $distFilePath, $isDeleteLocal = false) { $client = new Client([ 'base_uri' => C('PACKAGE_CHUNK_URL'), 'timeout' => 10.0, ]); $response = $client->post('/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' => '请求打包失败', ]; } return $result; } public function uploadPackageOnce($localFilePath, $distFilePath, $isDeleteLocal = false) { $ossService = new OssService(); $result = $ossService->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 = ''; if ($type == 'pack') { if ($promoteId == 0) { $fileName = 'Uploads/SourcePlist/'. $gameId . '.Plist'; } elseif ($platformId > 0) { $fileName = 'Uploads/GamePlist/'. $gameId . '-' . $promoteId . '-' . $platformId . '-' .$position . '.Plist'; } else { $fileName = 'Uploads/GamePlist/'. $gameId . '-' . $promoteId . '.Plist'; } } elseif ($type == 'org') { $fileName = 'Uploads/OrgSourcePlist/'. $gameId . '.Plist'; } $xml->save('./' . $fileName); return [ 'status' => true, 'message' => '生成成功', 'data' => [ 'path' => './' . $fileName ] ]; } }