Douyin.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. <?php
  2. /***
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. namespace app\utils\Douyin;
  8. use app\models\StoreDouyinMini;
  9. use app\models\Option;
  10. use app\constants\OptionSetting;
  11. class Douyin {
  12. public static $baseUrl = 'https://open.microapp.bytedance.com/';
  13. public static $component_conf = [];
  14. public static $mini_conf = [];
  15. public static $authorizer_access_token_refresh = [];
  16. public static $webviewHostName = 'ws2.cyyvip.com';
  17. public static $webviewHostInfo = 'https://ws2.cyyvip.com';
  18. public static function mini_conf($mini_id, $setConf = [], $refresh = 0) {
  19. $setConf['expires_in'] && $setConf['expires_in'] += time();
  20. $setConf['refresh_expires_in'] && $setConf['refresh_expires_in'] += time();
  21. $confDef = [
  22. 'authorizer_appid' => '',
  23. 'authorizer_access_token' => '',
  24. 'expires_in' => '',
  25. 'authorizer_refresh_token' => '',
  26. 'refresh_expires_in' => '',
  27. 'authorize_permission' => [],
  28. ];
  29. if($mini_id && ($refresh || !self::$mini_conf[$mini_id])){
  30. $mini = StoreDouyinMini::findOne($mini_id);
  31. self::$mini_conf[$mini_id] = json_decode($mini['auth'], true);
  32. }
  33. if($mini_id && self::$mini_conf[$mini_id]){
  34. $confDef = array_merge($confDef, self::$mini_conf[$mini_id]);
  35. if(!self::$authorizer_access_token_refresh[$mini_id] && $confDef['expires_in'] < (time() + 30 * 60)){
  36. $token_refresh = self::authorizer_access_token_refresh($mini_id, $confDef['authorizer_appid']);
  37. if($token_refresh['code'] == 0){
  38. $confDef = array_merge($confDef, $token_refresh['data']);
  39. }
  40. }
  41. }
  42. if($setConf){
  43. $confDef = array_merge($confDef, $setConf);
  44. }
  45. $mini_app_init = 0;
  46. if(!$mini_id && $confDef['authorizer_appid']){
  47. $mini_app_init = 1;
  48. $mini = StoreDouyinMini::authUpdate($mini_id, $confDef);
  49. if($mini['code'] != 0){
  50. throw new \Exception($mini['msg']);
  51. }
  52. $mini_id = $mini['data']['id'];
  53. }
  54. //进程内需要先更新self::$mini_conf[$mini_id],才能进行下一次调用接口
  55. $mini_id && self::$mini_conf[$mini_id] = $confDef;
  56. if($mini_app_init){
  57. debug_log([$mini_id, self::$mini_conf[$mini_id]], __CLASS__ . '.log');
  58. self::mini_app_init($mini_id);
  59. }
  60. return $confDef;
  61. }
  62. public static function component_conf($setConf = [], $refresh = 0) {
  63. $confDef = [
  64. 'component_appid' => '',
  65. 'component_appsecret' => '',
  66. 'aesKey' => '',
  67. 'tpToken' => '',
  68. 'template_id' => '',
  69. 'component_access_token' => '',
  70. 'webview_file' => 0,
  71. 'service_ip' => gethostbyname(\Yii::$app->request->hostName),
  72. 'hostName' => \Yii::$app->request->hostName,
  73. 'webviewHostName' => self::$webviewHostName,
  74. 'callback_component_ticket_url' => \Yii::$app->request->hostInfo . '/index.php/douyin/ticket',
  75. 'callback_component_event_url' => \Yii::$app->request->hostInfo . '/index.php/douyin/callback/$APPID$',
  76. 'callback_component_code_url' => \Yii::$app->request->hostInfo . '/index.php/douyin/store-mini-code/$STORE_ID$',
  77. ];
  78. if($refresh || !self::$component_conf){
  79. self::$component_conf = json_decode(Option::get(OptionSetting::DOUYIN_COMPONENT, 0, 'saas', '{}')['value'], true);
  80. }
  81. if(self::$component_conf){
  82. $confDef = array_merge($confDef, self::$component_conf);
  83. }
  84. if($setConf){
  85. $confDef = array_merge($confDef, $setConf);
  86. Option::set(OptionSetting::DOUYIN_COMPONENT, json_encode($confDef), 0, 'saas');
  87. }
  88. self::$component_conf = $confDef;
  89. return self::$component_conf;
  90. }
  91. public static function component_event_url($mini_id = 0) {
  92. $conf = self::component_conf();
  93. return str_replace('$APPID$', $mini_id, $conf['callback_component_event_url']);
  94. }
  95. public static function component_code_url($store_id = 0) {
  96. $conf = self::component_conf();
  97. return str_replace('$STORE_ID$', $store_id, $conf['callback_component_code_url']);
  98. }
  99. /***
  100. * 验证消息签名
  101. * @param $timestamp, $nonce, $encrypt, $msgSignature
  102. */
  103. public static function _component_verify($msg) {
  104. $conf = self::component_conf();
  105. $values = array($conf['tpToken'], $msg['TimeStamp'], $msg['Nonce'], $msg['Encrypt']);
  106. sort($values, SORT_STRING);
  107. $newMsgSignature = sha1(join("", $values));
  108. if ($newMsgSignature == $msg['MsgSignature']) {
  109. return [
  110. 'code' => 0,
  111. 'msg' => 'ok',
  112. ];
  113. } else {
  114. \Yii::error('签名验证失败');
  115. return [
  116. 'code' => 1,
  117. 'msg' => '签名验证失败',
  118. ];
  119. }
  120. }
  121. //三方消息解密
  122. public static function _component_decrypt($encrypted) {
  123. $conf = self::component_conf();
  124. $aesKey = base64_decode($conf['aesKey'] . "=");
  125. try {
  126. $blockSize = 32;
  127. // 使用BASE64对需要解密的字符串进行解码
  128. $ciphertextDec = base64_decode($encrypted);
  129. $iv = substr($aesKey, 0, 16);
  130. // 解密
  131. $decrypted = openssl_decrypt($ciphertextDec, 'aes-256-cbc', $aesKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
  132. } catch (\Exception $e) {
  133. \Yii::error($e);
  134. return [
  135. 'code' => 1,
  136. 'msg' => 'AesEncryptUtil AES解密串非法,小于16位;' . $e->getMessage(),
  137. ];
  138. }
  139. try {
  140. // 去除补位字符
  141. $pad = ord(substr($decrypted, -1));
  142. if ($pad < 1 || $pad > $blockSize) {
  143. $pad = 0;
  144. }
  145. $result = substr($decrypted, 0, (strlen($decrypted) - $pad));
  146. // $result = $this->decode($decrypted);
  147. // 去除16位随机字符串,网络字节序和 tp appid
  148. if (strlen($result) < 16) {
  149. throw new Exception('AesEncryptUtil AES解密串非法,小于16位;');
  150. }
  151. // 去除16位随机字符串
  152. $content = substr($result, 16, strlen($result));
  153. // 获取消息体长度
  154. $lenList = unpack("N", substr($content, 16, 4));
  155. $postBodyLen = $lenList[1];
  156. // 获取消息体
  157. $postBodyMsg = substr($content, 20, $postBodyLen);
  158. // 获取消息体的第三方平台 appid
  159. $fromTpAppId = substr($content, 20 + $postBodyLen);
  160. } catch (\Exception $e) {
  161. \Yii::error($e);
  162. return [
  163. 'code' => 1,
  164. 'msg' => 'AesEncryptUtil AES解密串非法,小于16位;' . $e->getMessage(),
  165. ];
  166. }
  167. return [
  168. 'code' => 0,
  169. 'data' => json_decode($postBodyMsg, true),
  170. 'msg' => '操作成功',
  171. ];
  172. }
  173. public static function msg_decode($msg) {
  174. $verify = self::_component_verify($msg);
  175. if ($verify['code'] != 0) {
  176. throw new \Exception('接口消息验签失败');
  177. // return $verify;
  178. }
  179. $encrypted = $msg['Encrypt'];
  180. $data = self::_component_decrypt($encrypted);
  181. if ($data['code'] != 0) {
  182. throw new \Exception('接口消息解密失败');
  183. // return $data;
  184. }
  185. $decode = $data['data'];
  186. unset($msg['Nonce']);
  187. unset($msg['TimeStamp']);
  188. unset($msg['Encrypt']);
  189. unset($msg['MsgSignature']);
  190. $res = array_merge($msg, $decode);
  191. // debug_log($res, __CLASS__ . '.log');
  192. return $res;
  193. }
  194. //三方ticket
  195. public static function component_ticket($component_ticket) {
  196. $time = time();
  197. // debug_log($component_ticket, __CLASS__ . '.log');
  198. $ticket = $component_ticket['Ticket'];
  199. $access_token = self::component_access_token($ticket);
  200. if ($access_token['code'] != 0) {
  201. return $access_token;
  202. }
  203. self::component_conf([
  204. 'component_access_token' => $access_token['data']['component_access_token'],
  205. 'expires_in' => $access_token['data']['expires_in'] + $time,
  206. ]);
  207. return $access_token;
  208. }
  209. //三方url
  210. public static function component_api_url($uri) {
  211. $url = self::$baseUrl . $uri;
  212. return $url;
  213. }
  214. //三方get
  215. public static function component_http_get($uri, $options) {
  216. $res = self::http_res(http_get(self::component_api_url($uri), $options));
  217. if($res['code'] != 0){
  218. debug_log([$uri, $options, $res], __CLASS__ . '.log');
  219. }
  220. return $res;
  221. }
  222. //三方post
  223. public static function component_http_post($uri, $options) {
  224. $res = self::http_res(http_post(self::component_api_url($uri), $options));
  225. if($res['code'] != 0){
  226. debug_log([$uri, $options, $res], __CLASS__ . '.log');
  227. }
  228. return $res;
  229. }
  230. public static function http_res($http_res) {
  231. if ($http_res->getStatusCode() != 200) {
  232. return [
  233. 'code' => 1,
  234. 'msg' => '接口请求http出错!',
  235. ];
  236. }
  237. $ContentType = implode(';', $http_res->getHeader('Content-Type'));
  238. if(strstr($ContentType, 'json')){
  239. $content = json_decode((string)$http_res->getBody(), true);
  240. }elseif(strstr($ContentType, 'image')){
  241. $content = 'data:' . $ContentType . ';base64,' . base64_encode((string)$http_res->getBody());
  242. }elseif(strstr($ContentType, 'text/plain')){
  243. $fileName = '';
  244. $fileHeader = $http_res->getHeader('Content-Disposition');
  245. if($fileHeader){
  246. foreach(explode(';', $fileHeader[0]) as $item){
  247. if(strstr($item, 'filename')){
  248. $fileName = trim(str_replace('filename=', '', $item));
  249. }
  250. }
  251. }
  252. if(empty($fileName)){
  253. return [
  254. 'code' => 1,
  255. 'msg' => '接口返回文件错误!',
  256. ];
  257. }
  258. $content = [
  259. 'fileName' => $fileName,
  260. 'fileContent' => (string)$http_res->getBody(),
  261. ];
  262. }else{
  263. $content = (string)$http_res->getBody();
  264. }
  265. $res = [
  266. 'code' => 0,
  267. 'data' => $content,
  268. 'msg' => '操作成功',
  269. ];
  270. if(isset($content['errno']) && $content['errno'] != 0){
  271. $res['code'] = $content['errno'];
  272. $res['msg'] = $content['message'];
  273. }
  274. if(isset($content['err_no']) && $content['err_no'] != 0){
  275. $res['code'] = $content['err_no'];
  276. $res['msg'] = $content['err_msg'];
  277. }
  278. return $res;
  279. }
  280. //三方调用时的query
  281. public static function http_component_query($query = []) {
  282. $conf = self::component_conf();
  283. $ret = array_merge([
  284. 'component_appid' => $conf['component_appid'],
  285. 'component_access_token' => $conf['component_access_token'],
  286. ], $query);
  287. return $ret;
  288. }
  289. //三方调用管理小程序时的query
  290. public static function http_component_app_query($mini_id = 0, $query = []) {
  291. $conf = self::component_conf();
  292. $mini_conf = self::mini_conf($mini_id);
  293. $ret = array_merge([
  294. 'component_appid' => $conf['component_appid'],
  295. 'authorizer_access_token' => $mini_conf['authorizer_access_token'],
  296. ], $query);
  297. return $ret;
  298. }
  299. //三方access_token
  300. public static function component_access_token($component_ticket) {
  301. $conf = self::component_conf();
  302. $data = [
  303. 'component_appid' => $conf['component_appid'],
  304. 'component_appsecret' => $conf['component_appsecret'],
  305. 'component_ticket' => $component_ticket,
  306. ];
  307. $res = self::component_http_get('openapi/v1/auth/tp/token', ['query' => $data]);
  308. return $res;
  309. }
  310. //三方小程序应用上传资源
  311. public static function tp_upload_material($material_type, $material_file) {
  312. $data = self::httpUpload([
  313. 'material_file' => self::saveTempImage($material_file),
  314. ], [
  315. 'material_type' => $material_type,
  316. ]);
  317. $res = self::component_http_post('openapi/v1/tp/upload_pic_material', ['query' => self::http_component_query(), 'multipart' => $data]);
  318. if($res['code'] != 0){
  319. return $res;
  320. }
  321. if($res['data']['errno'] == 0){
  322. $res['data'] = $res['data']['data']['path'];
  323. }
  324. return $res;
  325. }
  326. //代授权小程序上传资源
  327. public static function mini_upload_material($mini_id, $material_type, $material_file) {
  328. $data = self::httpUpload([
  329. 'material_file' => self::saveTempImage($material_file),
  330. ], [
  331. 'material_type' => $material_type,
  332. ]);
  333. $res = self::component_http_post('openapi/v1/microapp/upload_material', ['query' => self::http_component_app_query($mini_id), 'multipart' => $data]);
  334. if($res['code'] != 0){
  335. return $res;
  336. }
  337. if($res['data']['errno'] == 0){
  338. $res['data'] = $res['data']['data']['path'];
  339. }
  340. return $res;
  341. }
  342. //获取网络图片到临时目录
  343. private static function saveTempImage($url)
  344. {
  345. if (strpos($url, 'http') === false) {
  346. $url = 'http:' . trim($url);
  347. }
  348. if (!is_dir(\Yii::$app->runtimePath . '/image')) {
  349. mkdir(\Yii::$app->runtimePath . '/image');
  350. }
  351. $save_path = \Yii::$app->runtimePath . '/image/' . md5($url) . '.jpg';
  352. \app\utils\CurlHelper::download($url, $save_path);
  353. return $save_path;
  354. }
  355. public static function httpUpload(array $files = [], array $form = [])
  356. {
  357. $multipart = [];
  358. $headers = [];
  359. foreach ($files as $name => $path) {
  360. $multipart[] = [
  361. 'name' => $name,
  362. 'contents' => fopen($path, 'r'),
  363. 'headers' => $headers
  364. ];
  365. }
  366. foreach ($form as $name => $contents) {
  367. $multipart[] = compact('name', 'contents');
  368. }
  369. return $multipart;
  370. }
  371. //三方下载webview域名校验文件
  372. public static function webview_file() {
  373. $res = self::component_http_get('openapi/v1/tp/download/webview_file', ['query' => self::http_component_query()]);
  374. if($res['code'] != 0){
  375. return $res;
  376. }
  377. $addConfirmFile = self::addConfirmFile($res['data']);
  378. if($addConfirmFile['code'] != 0){
  379. return $addConfirmFile;
  380. }
  381. self::component_conf([
  382. 'webview_file' => 1,
  383. ]);
  384. return $res;
  385. }
  386. //校验文件提交至客服系统
  387. public static function addConfirmFile($file){
  388. $res = http_post(self::$webviewHostInfo . '/web/index.php?r=common/default/add-confirm-file', [
  389. 'form_params' => [
  390. 'token' => 'tianxin100',
  391. 'file_name' => $file['fileName'],
  392. 'file_content' => $file['fileContent'],
  393. ]
  394. ]);
  395. if ($res->getStatusCode() != 200) {
  396. return [
  397. 'code' => 1,
  398. 'msg' => '添加校验文件出错',
  399. ];
  400. }
  401. return [
  402. 'code' => 0,
  403. 'msg' => 'ok',
  404. ];
  405. }
  406. //code2session
  407. public static function code2session($mini_id = 0, $code = '') {
  408. $data = [
  409. 'code' => $code,
  410. ];
  411. $res = self::component_http_get('openapi/v1/microapp/code2session', ['query' => self::http_component_app_query($mini_id, $data)]);
  412. return $res;
  413. }
  414. //三方获取授权小程序列表
  415. public static function get_auth_app_list($fresh = 0,$page = null) {
  416. if($page === null){
  417. $cacheK = 'douyin_component_get_auth_app_list';
  418. $cacheV = cache()->get($cacheK);
  419. if(!$fresh && $cacheV){
  420. return [
  421. 'code' => 0,
  422. 'data' => $cacheV,
  423. ];
  424. }
  425. }
  426. $size = 50;
  427. $data = [
  428. 'page' => $page ?: 1,
  429. 'size' => $size,
  430. ];
  431. $res = self::component_http_get('openapi/v1/tp/auth_app_list', ['query' => self::http_component_query($data)]);
  432. if($res['code'] != 0){
  433. return $res;
  434. }
  435. $list = $res['data']['data'];
  436. $total = $list['total'];
  437. $ret = array_column($list['authAppList'], 'authAppId');
  438. if($page === null){
  439. $pageCount = ceil($total/$size);
  440. if($pageCount > 1){
  441. for($i = 2; $i <= $pageCount; $i++){
  442. $res = self::get_auth_app_list($page);
  443. $ret = array_merge($ret, $res['data']);
  444. }
  445. }
  446. cache()->set($cacheK, $ret);
  447. }
  448. return [
  449. 'code' => 0,
  450. 'data' => $ret,
  451. ];
  452. }
  453. //三方获取模板小程序列表(模板草稿列表)
  454. public static function get_tpl_app_list() {
  455. $res = self::component_http_get('openapi/v1/tp/template/get_tpl_app_list', ['query' => self::http_component_query()]);
  456. return $res;
  457. }
  458. //三方获取模板列表
  459. public static function get_tpl_list() {
  460. $res = self::component_http_get('openapi/v1/tp/template/get_tpl_list', ['query' => self::http_component_query()]);
  461. return $res;
  462. }
  463. //三方添加模板
  464. public static function add_tpl($draft_id = 0) {
  465. $data = [
  466. 'draft_id' => $draft_id,
  467. ];
  468. $res = self::component_http_post('openapi/v1/tp/template/add_tpl', ['query' => self::http_component_query(), 'json' => $data]);
  469. $res['msg'] .= ';或检查是否已经添加过。';
  470. return $res;
  471. }
  472. //三方删除模板
  473. public static function del_tpl($template_id = 0) {
  474. $data = [
  475. 'template_id' => $template_id,
  476. ];
  477. $res = self::component_http_post('openapi/v1/tp/template/del_tpl', ['query' => self::http_component_query(), 'json' => $data]);
  478. return $res;
  479. }
  480. //三方直接获取授权链接
  481. public static function component_pre_auth_code($store_id = 0, $link_type = 1, $app_name = '', $app_icon = '') {
  482. $limit = 5;
  483. $cacheKey = 'component_pre_auth_code_' . date('Ymd') . '_' . $store_id;
  484. $cacheV = cache()->get($cacheKey);
  485. if($link_type == 2){
  486. if($cacheV >= $limit){
  487. return [
  488. 'code' => 1,
  489. 'msg' => '操作失败,代创建操作次数超过店铺每天调用限制次数' . $cacheV . '/' . $limit,
  490. ];
  491. }
  492. }
  493. if($app_icon){
  494. $material = self::tp_upload_material(2, $app_icon);
  495. if($material['code'] != 0){
  496. return $material;
  497. }
  498. $app_icon = $material['data'];
  499. }
  500. $data = [
  501. 'link_type' => $link_type,
  502. 'redirect_uri' => self::component_code_url($store_id),
  503. ];
  504. $app_name && $data['app_name'] = $app_name;
  505. $app_name && $data['app_icon'] = $app_icon;
  506. $res = self::component_http_post('openapi/v2/auth/gen_link', ['query' => self::http_component_query(), 'json' => $data]);
  507. if($res['code'] == 0 && $link_type == 2){
  508. cache()->set($cacheKey, $cacheV + 1, 86400);
  509. $res['api_times'] = $cacheV + 1;
  510. }else{
  511. $res['api_times'] = $cacheV;
  512. }
  513. return $res;
  514. }
  515. //三方获取授权小程序接口调用凭据
  516. public static function authorizer_access_token($store_id, $mini_id, $authorization_code) {
  517. $data = [
  518. 'authorization_code' => $authorization_code,
  519. 'grant_type' => 'app_to_tp_authorization_code',
  520. ];
  521. $res = self::component_http_get('openapi/v1/oauth/token', ['query' => self::http_component_query($data)]);
  522. if ($res['code'] == 0) {
  523. self::mini_conf($mini_id, array_merge($res['data'], ['store_id' => $store_id]));
  524. }
  525. return $res;
  526. }
  527. //三方找回小程序授权码
  528. public static function authorization_code($appid = '') {
  529. $data = [
  530. 'authorization_appid' => $appid,
  531. ];
  532. $res = self::component_http_post('openapi/v1/auth/retrieve', ['query' => self::http_component_query($data)]);
  533. return $res;
  534. }
  535. //授权小程序access_token刷新
  536. public static function authorizer_access_token_refresh($mini_id = 0, $appid = '') {
  537. self::$authorizer_access_token_refresh[$mini_id] = 1;
  538. $authorization_code = self::authorization_code($appid);
  539. if ($authorization_code['code'] != 0) {
  540. return $authorization_code;
  541. }
  542. $mini = StoreDouyinMini::findOne($mini_id);
  543. $authorizer_access_token = self::authorizer_access_token($mini['store_id'], $mini_id, $authorization_code['data']['authorization_code']);
  544. self::$authorizer_access_token_refresh[$mini_id] = 0;
  545. return $authorizer_access_token;
  546. }
  547. //小程序初始化
  548. public static function mini_app_init($mini_id = 0) {
  549. try {
  550. $cacheK = 'Douyin_mini_app_init_' . $mini_id;
  551. $cacheV = cache()->get($cacheK);
  552. if($cacheV){
  553. return;
  554. }
  555. cache()->set($cacheK, 1, 10);
  556. $site = [\Yii::$app->request->serverName];
  557. self::modify_server_domain($mini_id, 'add', $site, $site, $site, $site);
  558. self::modify_webview_domain($mini_id, 'add', [self::$webviewHostName]);
  559. $cate = self::categories($mini_id);
  560. //如果没有设置过类目
  561. if($cate['code'] == 0 && ($cate['data']['data']['remainingTimes'] == $cate['data']['data']['categoryChangeLimit'])){
  562. //工具 - 实用工具-记账
  563. // $categoryInfoInit = [
  564. // [
  565. // "category" => "188,189,190",
  566. // ]
  567. // ];
  568. // self::add_categories($mini_id, $categoryInfoInit);
  569. }
  570. } catch (\Exception $e) {
  571. \Yii::error($e);
  572. }
  573. }
  574. //三方获取是否授权小程序
  575. public static function has_auth_app($appid = '') {
  576. $list = self::get_auth_app_list();
  577. if($list['code'] != 0){
  578. return $list;
  579. }
  580. $in_array = in_array($appid, $list['data']);
  581. if(!$in_array){
  582. return [
  583. 'code' => 1,
  584. 'msg' => '未授权',
  585. ];
  586. }
  587. return [
  588. 'code' => 0,
  589. 'msg' => '已授权',
  590. ];
  591. }
  592. /**
  593. * 获取店铺小程序列表
  594. * $simple 是否只获取简单数据
  595. */
  596. public static function mini_app_list($store_id = 0, $simple = 0, $search = []) {
  597. $query = StoreDouyinMini::find()->where(['store_id' => $store_id])->orderBy('id DESC');
  598. if($search['app_name']){
  599. $query->andWhere(['like', 'app_name', $search['app_name']]);
  600. }
  601. $data = pagination_make($query, false);
  602. $list = [];
  603. foreach($data['list'] as $item){
  604. $_list = $item->attributes;
  605. if(!$simple){
  606. $_has_auth_app = !self::has_auth_app($item['auth_app_id'])['code'];
  607. if($_has_auth_app){
  608. $appInfo = self::mini_app_info($item['id']);
  609. try {
  610. if($appInfo['code'] == 0){
  611. $minfo = $appInfo['data']['data'];
  612. $item->app_name = $minfo['app_name'];
  613. $item->app_logo = $minfo['app_icon'];
  614. $item->app_desc = $minfo['app_intro'];
  615. $item->save();
  616. }
  617. } catch (\Exception $e) {
  618. \Yii::error($e);
  619. }
  620. $_list = $item->attributes;
  621. $_list['_mini_app_info'] = $appInfo;
  622. $_list['_mini_versions'] = self::mini_versions($item['id']);
  623. }
  624. $_list['_has_auth_app'] = $_has_auth_app;
  625. }
  626. $list[] = $_list;
  627. }
  628. return [
  629. 'code' => 0,
  630. 'data' => $list,
  631. ];
  632. }
  633. //获取小程序基本信息
  634. public static function mini_app_info($mini_id = 0) {
  635. $res = self::component_http_get('openapi/v1/microapp/app/info', ['query' => self::http_component_app_query($mini_id)]);
  636. if($res['code'] == 0){
  637. $data = $res['data']['data'];
  638. $baseStatus = [];
  639. $baseStatus[] = $data['new_name_audit_info']['new_name_audit_state'];
  640. $baseStatus[] = $data['new_icon_audit_info']['new_icon_audit_state'];
  641. $baseStatus[] = $data['new_intro_audit_info']['new_intro_audit_state'];
  642. $res['data']['data']['baseStatus'] = 2;
  643. if(in_array(3, $baseStatus)){
  644. $res['data']['data']['baseStatus'] = 3;
  645. }
  646. if(in_array(1, $baseStatus)){
  647. $res['data']['data']['baseStatus'] = 1;
  648. }
  649. }
  650. return $res;
  651. }
  652. //获取二维码
  653. public static function mini_app_qrcode($mini_id = 0, $version = 'current', $path = '') {
  654. $data = [
  655. 'version' => $version,
  656. 'path' => $path,
  657. ];
  658. $res = self::component_http_post('openapi/v1/microapp/app/qrcode', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  659. return $res;
  660. }
  661. //小程序名称检测
  662. public static function check_app_name($mini_id = 0, $app_name = '') {
  663. $data = [
  664. 'app_name' => $app_name,
  665. ];
  666. $res = self::component_http_get('openapi/v1/microapp/app/check_app_name', ['query' => self::http_component_app_query($mini_id, $data)]);
  667. return $res;
  668. }
  669. /***
  670. * 修改小程序名称
  671. * $material_file_path 小程序名称审核材料路径
  672. 注意:
  673. 1. 如果调用小程序名称检测接口,返回 21006 错误,则必须先调用代授权小程序上传资源接口上传证明。
  674. 2. 需要使用代授权小程序上传资源接口返回的路径才可以,接口调用时入参 material_type 字段为 3,否则报错。
  675. */
  676. public static function modify_app_name($mini_id = 0, $app_name = '', $material_file_path = '') {
  677. $check_app_name = self::check_app_name($mini_id, $app_name);
  678. if($check_app_name['code'] == 21006 && empty($material_file_path)){
  679. return $check_app_name;
  680. }
  681. if($material_file_path){
  682. $material = self::mini_upload_material($mini_id, 3, $material_file_path);
  683. if($material['code'] != 0){
  684. return $material;
  685. }
  686. $material_file_path = $material['data'];
  687. }
  688. $data = [
  689. 'new_name' => $app_name,
  690. ];
  691. $material_file_path && $data['material_file_path'] = $material_file_path;
  692. $res = self::component_http_post('openapi/v1/microapp/app/modify_app_name', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  693. return $res;
  694. }
  695. //修改小程序简介
  696. public static function modify_app_intro($mini_id = 0, $new_intro = '') {
  697. $data = [
  698. 'new_intro' => $new_intro,
  699. ];
  700. $res = self::component_http_post('openapi/v1/microapp/app/modify_app_intro', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  701. return $res;
  702. }
  703. /**
  704. * 修改小程序图标
  705. * $new_icon_path 授权小程序准备修改的图标路径
  706. 需要使用代授权小程序上传资源接口返回的路径才可以,接口调用时入参 material_type 字段为 1,否则报错。
  707. */
  708. public static function modify_app_icon($mini_id = 0, $new_icon_path = '') {
  709. if($new_icon_path){
  710. $material = self::mini_upload_material($mini_id, 1, $new_icon_path);
  711. if($material['code'] != 0){
  712. return $material;
  713. }
  714. $new_icon_path = $material['data'];
  715. }
  716. $data = [
  717. 'new_icon_path' => $new_icon_path,
  718. ];
  719. $res = self::component_http_post('openapi/v1/microapp/app/modify_app_icon', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  720. return $res;
  721. }
  722. //修改小程序服务器域名
  723. public static function modify_server_domain($mini_id = 0, $action = 'get', $request = [], $socket = [], $upload = [], $download = []) {
  724. $data = [
  725. 'action' => $action,
  726. 'request' => $request,
  727. 'socket' => $socket,
  728. 'upload' => $upload,
  729. 'download' => $download,
  730. ];
  731. $res = self::component_http_post('openapi/v1/microapp/app/modify_server_domain', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  732. return $res;
  733. }
  734. //修改小程序webview域名
  735. public static function modify_webview_domain($mini_id = 0, $action = 'get', $webview = []) {
  736. $data = [
  737. 'action' => $action,
  738. 'webview' => $webview,
  739. ];
  740. $res = self::component_http_post('openapi/v1/microapp/app/modify_webview_domain', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  741. return $res;
  742. }
  743. /**
  744. * 获取可以设置的服务类目列表
  745. */
  746. public static function all_categories($mini_id = 0) {
  747. $res = self::component_http_get('openapi/v1/microapp/app/all_categories', ['query' => self::http_component_app_query($mini_id)]);
  748. return $res;
  749. }
  750. /**
  751. * 获取已设置的服务类目
  752. */
  753. public static function categories($mini_id = 0) {
  754. $res = self::component_http_get('openapi/v1/microapp/app/categories', ['query' => self::http_component_app_query($mini_id)]);
  755. return $res;
  756. }
  757. /**
  758. * 增加服务类目
  759. */
  760. public static function add_categories($mini_id = 0, $categoryInfo = []) {
  761. foreach($categoryInfo as &$item){
  762. if(isset($item['categoryCert']) && empty($item['categoryCert'])){
  763. unset($item['categoryCert']);
  764. }
  765. if($item['categoryCert']){
  766. foreach($item['categoryCert'] as $i => $cert){
  767. if(!empty($cert['path'])){
  768. $material = self::mini_upload_material($mini_id, 2, $cert['path']);
  769. if($material['code'] != 0){
  770. return $material;
  771. }
  772. $item['categoryCert'][$i]['path'] = $material['data'];
  773. }
  774. if(!empty($cert['pathList'])){
  775. foreach($cert['pathList'] as $k => $path){
  776. $material = self::mini_upload_material($mini_id, 2, $path);
  777. if($material['code'] != 0){
  778. return $material;
  779. }
  780. $item['categoryCert'][$i]['pathList'][$k] = $material['data'];
  781. }
  782. }
  783. }
  784. }
  785. }
  786. $data = [
  787. 'categoryInfo' => $categoryInfo,
  788. ];
  789. // var_dump($data);die;
  790. $res = self::component_http_post('openapi/v1/microapp/app/add_categories', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  791. return $res;
  792. }
  793. /**
  794. * 删除服务类目
  795. */
  796. public static function del_categories($mini_id = 0, $categories = []) {
  797. $data = [
  798. 'categories' => $categories,
  799. ];
  800. $res = self::component_http_post('openapi/v1/microapp/app/del_categories', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  801. return $res;
  802. }
  803. /**
  804. * 提交代码
  805. */
  806. public static function mini_upload($mini_id = 0, $template_id = '', $user_desc = '', $user_version = '') {
  807. $user_version || $user_version = date('y.md.Hi');
  808. $user_desc || $user_desc = 'desc';
  809. $conf = self::component_conf();
  810. $mini_conf = self::mini_conf($mini_id);
  811. $mini = StoreDouyinMini::findOne($mini_id);
  812. if(empty($template_id)){
  813. $template_id = $conf['template_id'];
  814. }
  815. $ext_json = [
  816. 'extEnable' => true,
  817. 'extAppid' => $mini_conf['authorizer_appid'],
  818. 'ext' => [
  819. 'store_id' => $mini['store_id'],
  820. 'mini_id' => $mini_id,
  821. "apiurl" => "https://" . \Yii::$app->request->hostName,
  822. ],
  823. ];
  824. $data = [
  825. 'template_id' => (int)$template_id,
  826. 'user_desc' => $user_desc,
  827. 'user_version' => $user_version,
  828. 'ext_json' => json_encode($ext_json),
  829. ];
  830. $res = self::component_http_post('openapi/v1/microapp/package/upload', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  831. return $res;
  832. }
  833. /**
  834. * 提审代码
  835. */
  836. public static function mini_audit($mini_id = 0, $auditNote = '', $auditWay = 0) {
  837. $data = [
  838. 'hostNames' => ['douyin'],
  839. 'auditWay' => $auditWay,
  840. ];
  841. $auditNote && $data['auditNote'] = $auditNote;
  842. $res = self::component_http_post('openapi/v2/microapp/package/audit', ['query' => self::http_component_app_query($mini_id), 'json' => $data]);
  843. return $res;
  844. }
  845. /**
  846. * 撤回审核
  847. */
  848. public static function mini_revoke_audit($mini_id = 0) {
  849. $res = self::component_http_post('openapi/v1/microapp/package/revoke_audit', ['query' => self::http_component_app_query($mini_id)]);
  850. return $res;
  851. }
  852. /**
  853. * 发布代码
  854. */
  855. public static function mini_release($mini_id = 0) {
  856. $res = self::component_http_post('openapi/v1/microapp/package/release', ['query' => self::http_component_app_query($mini_id)]);
  857. return $res;
  858. }
  859. /**
  860. * 回退代码版本
  861. */
  862. public static function mini_rollback($mini_id = 0) {
  863. $res = self::component_http_post('openapi/v1/microapp/package/release', ['query' => self::http_component_app_query($mini_id)]);
  864. return $res;
  865. }
  866. /**
  867. * 获取小程序版本列表信息
  868. */
  869. public static function mini_versions($mini_id = 0) {
  870. $res = self::component_http_get('openapi/v1/microapp/package/versions', ['query' => self::http_component_app_query($mini_id)]);
  871. return $res;
  872. }
  873. }