WechatMiniProgramUpload.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. <?php
  2. namespace common\models\wechat;
  3. use addons\StoreSteward\common\enums\StoreStewardEnum;
  4. use backend\modules\mall\services\MiniProgramService;
  5. use common\enums\AddonsEnum;
  6. use common\enums\StatusEnum;
  7. use common\models\common\MpwxPlugins;
  8. use common\models\mall\MallAddons;
  9. use Exception;
  10. use Throwable;
  11. use Yii;
  12. /**
  13. * This is the model class for table "{{%wechat_mini_program_upload}}".
  14. * @property int $id AUTO_INCREMEN
  15. * @property int $status 状态
  16. * @property string $output 输出
  17. * @property int $mall_id 商城ID
  18. * @property string $desc 描述
  19. * @property string $secret_key_file 上传密钥文件路径
  20. * @property string $app_id appid
  21. * @property string $version 版本号
  22. * @property string $api_domain 接口域名
  23. * @property string $static_domain 静态资源域名
  24. * @property string $mall_sign 商城签名
  25. * @property int $created_at 添加时间
  26. * @property int $updated_at 修改时间
  27. */
  28. class WechatMiniProgramUpload extends \common\models\base\BaseActiveRecord
  29. {
  30. /**
  31. * {@inheritdoc}
  32. */
  33. public static function tableName()
  34. {
  35. return '{{%wechat_mini_program_upload}}';
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public function rules()
  41. {
  42. return [
  43. [['mall_id', 'app_id', 'is_live', 'upload_secret_key', 'version'], 'required'],
  44. [['version', 'desc', 'upload_secret_key', 'api_domain', 'static_domain', 'mall_sign','output'], 'string'],
  45. [['is_live', 'is_review'], 'integer'],
  46. [['plugins',], 'safe']
  47. ];
  48. }
  49. /**
  50. * {@inheritdoc}
  51. */
  52. public function attributeLabels()
  53. {
  54. return [
  55. 'id' => 'ID',
  56. 'status' => ' 状态',
  57. 'output' => '输出',
  58. 'mall_id' => ' 商城ID',
  59. 'desc' => '描述',
  60. 'upload_secret_key' => '小程序上传密钥',
  61. 'app_id' => 'appid',
  62. 'version' => '版本号',
  63. 'is_review' => '是否审核',
  64. 'api_domain' => '接口域名',
  65. 'static_domain' => '静态资源域名',
  66. 'mall_sign' => '商城签名',
  67. 'created_at' => '添加时间',
  68. 'updated_at' => '修改时间',
  69. ];
  70. }
  71. /**
  72. * 添加任务
  73. * @param mixed $params
  74. * @throws \Exception
  75. * @return WechatMiniProgramUpload|bool
  76. */
  77. public static function addTask($params)
  78. {
  79. try {
  80. $model = new self();
  81. $params['desc'] = empty($params['desc']) ? '后台管理员于' . date('Y-m-d H:i:s') . '提交上传' : $params['desc'];
  82. $model->setAttributes($params);
  83. $res = $model->save();
  84. if ($res === false) throw new \Exception($model->getErrorMessage());
  85. return $model;
  86. } catch (\Exception $e) {
  87. self::$static_error = $e->getMessage();
  88. return false;
  89. }
  90. }
  91. /**
  92. * 上传最新微信小程序
  93. * @param mixed $params
  94. * @return void
  95. */
  96. public static function uploadMiniProgramCode($params)
  97. {
  98. try {
  99. $output = '['.date('Y-m-d H:i:s').'] 上传小程序任务队列开始...'.PHP_EOL;;
  100. $id = $params['id'];
  101. $task = self::find()->where(['id' => $id])->one();
  102. // if ($task->status == 1) return true;
  103. // 下载最新代码包,并替换全局参数
  104. $app_id = $task['app_id'];
  105. $api_domain = $task['api_domain'];
  106. $static_domain = $task['static_domain'];
  107. $mall_id = $task['mall_id'];
  108. $mall_sign = $task['mall_sign'];
  109. // $project_name = $task['is_live'] == 1 ? 'mp-weixin-live' : 'mp-weixin';
  110. $project_name = 'mp-weixin';
  111. // 替换为代码库的云域名
  112. $latest_mp_downlaod_url = \Yii::$app->params['latest_mp_downlaod_url'] ?? '';
  113. if(empty($latest_mp_downlaod_url)) throw new Exception('未设置远程下载链接');
  114. $url = $latest_mp_downlaod_url."/$project_name.zip";
  115. $tempDir = '/tmp';
  116. $filename = $tempDir."/$app_id.zip";
  117. $unzip_dir = $tempDir."/$app_id";
  118. $project_dir = "$unzip_dir/$project_name";
  119. $log_file = $unzip_dir.'/upload.log';
  120. if(file_exists($unzip_dir)) exec("rm -rf $unzip_dir");
  121. $output .= '['.date('Y-m-d H:i:s').'] 开始下载最新小程序代码包...'.PHP_EOL.'网址:'.$url.PHP_EOL;
  122. // 下载压缩包并解压
  123. self::downloadCode($url, $filename);
  124. exec("unzip $filename -d $unzip_dir && rm -rf $filename",$outstd, $status);
  125. if($status == 0){
  126. $output .= '['.date('Y-m-d H:i:s').'] 解压代码包成功:'. json_encode($outstd) .PHP_EOL;
  127. }else{
  128. throw new Exception('解压代码包失败:'."unzip $filename -d $unzip_dir && rm -rf $filename".PHP_EOL. json_encode($outstd) . PHP_EOL);
  129. $outstd = [];
  130. }
  131. // 地图密钥
  132. $config = Yii::$app->services->setting->platform->get('map');
  133. $qq_map_key = $config['qq_jsapi_key_h5'] ? $config['qq_jsapi_key_h5'] : 'TSOBZ-PHCWT-MPBX5-LZLSP-4WSW6-UIFVJ';
  134. $replace_items = [
  135. ['---mp_app_id---', $app_id],
  136. ['---api---', $api_domain],
  137. ['---static---', $static_domain],
  138. ['---sign---', $mall_sign],
  139. ['TSOBZ-PHCWT-MPBX5-LZLSP-4WSW6-UIFVJ', $qq_map_key], // 替换地图key
  140. ];
  141. $output .= '['.date('Y-m-d H:i:s').'] 参数替换列表:'.PHP_EOL.json_encode($replace_items).PHP_EOL;
  142. // 替换项目文件参数
  143. $output .= '['.date('Y-m-d H:i:s').'] 替换项目参数.'.PHP_EOL;
  144. foreach ($replace_items as $item) {
  145. $find_str = $item[0];
  146. $replace_str = $item[1];
  147. $files = self::findFileContainStr($project_dir, $find_str);
  148. if (!empty($files)) {
  149. // 替换参数
  150. foreach ($files as $file) {
  151. if (file_exists($file)) {
  152. $content = file_get_contents($file);
  153. file_put_contents($file, str_replace($find_str, $replace_str, $content));
  154. $output .= $find_str . '--->' . $replace_str . PHP_EOL;
  155. }
  156. }
  157. }
  158. }
  159. // 替换应用的扩展插件
  160. if ($task['is_live']) {
  161. $pluginsStr = '{';
  162. if (!empty($task['plugins'])) {
  163. $plugins = MpwxPlugins::find()
  164. ->where(['addons_name' => $task['plugins'], 'status' => StatusEnum::ENABLED])
  165. ->select('plugins')
  166. ->column();
  167. $pluginsStr .= implode(',', $plugins);
  168. }
  169. $pluginsStr .= '}';
  170. // 替换内容
  171. $command = <<<COMMAND
  172. sed -i 's/"plugins": {},/"plugins": $pluginsStr,/g' $project_dir/app.json
  173. COMMAND;
  174. $output .= '['.date('Y-m-d H:i:s').'] 替换插件参数:.' . $pluginsStr . PHP_EOL;
  175. exec($command, $outstd, $status);
  176. }
  177. //安装了门店管家插件的 更新首页路径
  178. $install = MallAddons::isInstall($mall_id, AddonsEnum::ADDONS_NAME_STORE_STEWARD);
  179. if ($install) {
  180. $service = new \addons\StoreSteward\common\service\StoreStewardService();
  181. $index_update_result = $service::updateIndexRoute($mall_id,$project_dir);
  182. if ($index_update_result['result']) {
  183. $output .= $index_update_result['output'];
  184. }
  185. }
  186. // 生成上传密钥文件
  187. $output .= '['.date('Y-m-d H:i:s').'] 生成上传密钥文件.'.PHP_EOL;
  188. $key_file = "$unzip_dir/$app_id.key";
  189. file_put_contents($key_file, $task['upload_secret_key']);
  190. // 上传小程序代码
  191. $node = \Yii::$app->params['node_bin'] ?? '';
  192. $upload_script = Yii::getAlias('@webpath').DIRECTORY_SEPARATOR.'node/mp-ci-upload.js';
  193. if(!$node || !file_exists($upload_script)) throw new Exception('node命令不存在 / js文件不存在');
  194. //$upload_script = '/www/wwwroot/mp_ci/mp-ci-upload.js';
  195. $script_params = "--appid $app_id --projectPath $project_dir --privateKeyPath $key_file --version {$task['version']} --desc '{$task['desc']}'";
  196. $log = "2>&1 > $log_file";
  197. $output .= '['.date('Y-m-d H:i:s').'] 组装上传命令.'.PHP_EOL;
  198. $command = "$node $upload_script $script_params $log";
  199. $output .= $command . PHP_EOL;
  200. $output .= '['.date('Y-m-d H:i:s').'] 执行上传命令.'.PHP_EOL;
  201. exec($command , $outstd, $status);
  202. $output .= file_get_contents($log_file) .PHP_EOL;
  203. $task->output = $output;
  204. // 成功
  205. $task->status = 1;
  206. $task->save();
  207. exec("rm -rf $unzip_dir");
  208. return true;
  209. } catch (Throwable $e) {
  210. $output .= $e->getMessage(). PHP_EOL;
  211. $task->output = $output;
  212. $task->status = 2;
  213. $task->save();
  214. Yii::error('上传小程序代码出错!uploadMiniProgramCode:' . $e->getMessage());
  215. return false;
  216. }
  217. }
  218. /**
  219. * 查找包含指定字符串的文件列表
  220. * @param mixed $project_dir
  221. * @param mixed $str
  222. * @return array|null
  223. */
  224. private static function findFileContainStr($project_dir, $str)
  225. {
  226. $output = [];
  227. exec("grep -Rl '\\$str' $project_dir", $output, $status);
  228. return $output;
  229. }
  230. /**
  231. * 下载最新小程序代码
  232. * @param mixed $url
  233. * @param mixed $filename
  234. * @return void
  235. */
  236. private static function downloadCode($url, $filename)
  237. {
  238. $ch = curl_init();
  239. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  240. curl_setopt($ch, CURLOPT_URL, $url);
  241. $fp = fopen($filename, 'w');
  242. curl_setopt($ch, CURLOPT_FILE, $fp);
  243. curl_exec($ch);
  244. curl_close($ch);
  245. fclose($fp);
  246. }
  247. /**
  248. * 获取文件临时目录
  249. */
  250. public static function getBackupDir()
  251. {
  252. $dir = Yii::$app->getRuntimePath().DIRECTORY_SEPARATOR.'bak'.DIRECTORY_SEPARATOR;
  253. if (!is_dir($dir)) {
  254. @mkdir($dir, 0755, true);
  255. }
  256. return $dir;
  257. }
  258. /**
  259. * 获取最新版本号
  260. *
  261. * @param integer $mall_id
  262. * @return string|null
  263. */
  264. public static function getLastVersion(int $mall_id)
  265. {
  266. return static::find()
  267. ->where(['mall_id' => $mall_id])
  268. ->orderBy('id DESC')
  269. ->select('version')
  270. ->scalar();
  271. }
  272. /**
  273. * 获取下一个版本号
  274. *
  275. * @param integer $mall_id
  276. * @return string
  277. */
  278. public static function getNextVersion(int $mall_id)
  279. {
  280. $version = static::getLastVersion($mall_id);
  281. if ($version === null) return '1.0.0';
  282. $parts = explode('.', $version);
  283. // x.y
  284. if (count($parts) == 2) {
  285. // 将最后一个部分转换为整数并增加1
  286. $parts[1] = (int)$parts[1] + 1;
  287. }
  288. // x.y.z
  289. if (count($parts) == 3) {
  290. // 将最后一个部分转换为整数并增加1
  291. $parts[2] = (int)$parts[2] + 1;
  292. }
  293. return implode('.', $parts);
  294. }
  295. /**
  296. * 修改任务审核状态
  297. *
  298. * @return boolean
  299. */
  300. public function changeTaskReview()
  301. {
  302. $this->is_review = intval(!$this->is_review);
  303. $this->changeAllTaskReview();
  304. return $this->save();
  305. }
  306. /**
  307. * 通过版本获取任务
  308. *
  309. * @param integer $mall_id
  310. * @param string $version
  311. * @param false|int $cache
  312. * @param string|array $field
  313. * @return static|null
  314. */
  315. public static function getTaskByVersion(int $mall_id, string $version, $cache = false, $field = 'id, is_review')
  316. {
  317. $model = static::find()->where(['mall_id' => $mall_id, 'version' => $version]);
  318. $cache && $model->cache($cache);
  319. $field && $model->select($field);
  320. return $model->one();
  321. }
  322. /**
  323. * 修改其他状态
  324. *
  325. * @return boolean
  326. */
  327. public function changeAllTaskReview()
  328. {
  329. // 打开当前审核审核 关闭其他审核
  330. if ($this->is_review) {
  331. $mall_id = $this->mall_id;
  332. static::updateAll(['is_review' => 0], ['mall_id' => $mall_id, 'is_review' => 1]);
  333. }
  334. }
  335. }