Weapp.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <?php
  2. /**
  3. * Niushop商城系统 - 团队十年电商经验汇集巨献!
  4. * =========================================================
  5. * Copy right 2019-2029 杭州牛之云科技有限公司, 保留所有权利。
  6. * ----------------------------------------------
  7. * 官方网址: https://www.niushop.com
  8. * =========================================================
  9. */
  10. namespace addon\weapp\model;
  11. use app\model\BaseModel;
  12. use app\model\system\Api;
  13. use EasyWeChat\Factory;
  14. use think\facade\Cache;
  15. use addon\weapp\model\Config as WeappConfigModel;
  16. use addon\wxoplatform\model\Config as WxOplatformConfigModel;
  17. use app\model\web\Config as WebConfig;
  18. /**
  19. * 微信小程序配置
  20. */
  21. class Weapp extends BaseModel
  22. {
  23. private $app;//微信模型
  24. //小程序类型
  25. public $service_type = array(
  26. 0 => "小程序",
  27. );
  28. //小程序认证类型
  29. public $verify_type = array(
  30. -1 => "未认证",
  31. 0 => "微信认证",
  32. );
  33. //business_info 说明
  34. public $business_type = array(
  35. 'open_store' => "是否开通微信门店功能",
  36. 'open_scan' => "是否开通微信扫商品功能",
  37. 'open_pay' => "是否开通微信支付功能",
  38. 'open_card' => "是否开通微信卡券功能",
  39. 'open_shake' => "是否开通微信摇一摇功能",
  40. );
  41. // 站点ID
  42. private $site_id;
  43. public function __construct($site_id = 0)
  44. {
  45. $this->site_id = $site_id;
  46. //微信小程序配置
  47. $weapp_config_model = new WeappConfigModel();
  48. $weapp_config = $weapp_config_model->getWeappConfig($site_id);
  49. $weapp_config = $weapp_config["data"]["value"];
  50. if (isset($weapp_config['is_authopen']) && addon_is_exit('wxoplatform')) {
  51. $plateform_config_model = new WxOplatformConfigModel();
  52. $plateform_config = $plateform_config_model->getOplatformConfig();
  53. $plateform_config = $plateform_config["data"]["value"];
  54. $config = [
  55. 'app_id' => $plateform_config["appid"] ?? '',
  56. 'secret' => $plateform_config["secret"] ?? '',
  57. 'token' => $plateform_config["token"] ?? '',
  58. 'aes_key' => $plateform_config["aes_key"] ?? '',
  59. 'log' => [
  60. 'level' => 'debug',
  61. 'permission' => 0777,
  62. 'file' => 'runtime/log/wechat/oplatform.logs',
  63. ],
  64. ];
  65. $open_platform = Factory::openPlatform($config);
  66. $this->app = $open_platform->miniProgram($weapp_config['authorizer_appid'], $weapp_config['authorizer_refresh_token']);
  67. } else {
  68. $config = [
  69. 'app_id' => $weapp_config["appid"] ?? '',
  70. 'secret' => $weapp_config["appsecret"] ?? '',
  71. 'response_type' => 'array',
  72. 'log' => [
  73. 'level' => 'debug',
  74. 'permission' => 0777,
  75. 'file' => 'runtime/log/wechat/easywechat.logs',
  76. ],
  77. ];
  78. $this->app = Factory::miniProgram($config);
  79. }
  80. }
  81. /**
  82. * TODO
  83. * 根据 jsCode 获取用户 session 信息
  84. * @param $param
  85. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  86. */
  87. public function authCodeToOpenid($param)
  88. {
  89. try {
  90. $result = $this->app->auth->session($param['code']);
  91. if (isset($result['errcode']) && $result['errcode'] != 0) {
  92. return $this->handleError($result['errcode'], $result['errmsg']);
  93. } else {
  94. Cache::set('weapp_' . $result['openid'], $result);
  95. unset($result['session_key']);
  96. return $this->success($result);
  97. }
  98. } catch (\Exception $e) {
  99. if (property_exists($e, 'formattedResponse')) {
  100. return $this->handleError($e->formattedResponse['errcode'], $e->formattedResponse['errmsg']);
  101. }
  102. return $this->error('', $e->getMessage());
  103. }
  104. }
  105. /**
  106. * 生成二维码
  107. * @param unknown $param
  108. */
  109. public function createQrcode($param)
  110. {
  111. try {
  112. $checkpath_result = $this->checkPath($param['qrcode_path']);
  113. if ($checkpath_result["code"] != 0) return $checkpath_result;
  114. // scene:场景值最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~
  115. $scene = '';
  116. if (!empty($param['data'])) {
  117. foreach ($param['data'] as $key => $value) {
  118. //防止参数过长,source_member用m代替
  119. if($key == 'source_member'){
  120. $key = 'm';
  121. }
  122. if ($scene == '') $scene .= $key . '-' . $value;
  123. else $scene .= '&' . $key . '-' . $value;
  124. }
  125. }
  126. $response = $this->app->app_code->getUnlimit($scene, [
  127. 'page' => substr($param['page'], 1),
  128. 'width' => isset($param['width']) ? $param['width'] : 120
  129. ]);
  130. if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
  131. $filename = $param['qrcode_path'] . '/';
  132. $filename .= $response->saveAs($param['qrcode_path'], $param['qrcode_name'] . '_' . $param['app_type'] . '.png');
  133. return $this->success(['type' => 'weapp', 'path' => $filename]);
  134. } else {
  135. return $this->handleError($response['errcode'], $response['errmsg']);
  136. }
  137. } catch (\Exception $e) {
  138. if (property_exists($e, 'formattedResponse')) {
  139. return $this->handleError($e->formattedResponse['errcode'], $e->formattedResponse['errmsg']);
  140. }
  141. return $this->error('', $e->getMessage());
  142. }
  143. }
  144. /**
  145. * 校验目录是否可写
  146. * @param unknown $path
  147. * @return multitype:number unknown |multitype:unknown
  148. */
  149. private function checkPath($path)
  150. {
  151. if (is_dir($path) || mkdir($path, intval('0755', 8), true)) {
  152. return $this->success();
  153. }
  154. return $this->error('', "directory {$path} creation failed");
  155. }
  156. /************************************************************* 数据统计与分析 start **************************************************************/
  157. /**
  158. * 访问日趋势
  159. * @param $from 格式 20170313
  160. * @param $to 格式 20170313
  161. */
  162. public function dailyVisitTrend($from, $to)
  163. {
  164. try {
  165. $result = $this->app->data_cube->dailyVisitTrend($from, $to);
  166. if (isset($result['errcode']) && $result['errcode'] != 0) {
  167. return $this->error([], $result["errmsg"]);
  168. }
  169. return $this->success($result["list"]);
  170. } catch (\Exception $e) {
  171. return $this->error([], $e->getMessage());
  172. }
  173. }
  174. /**
  175. * 访问周趋势
  176. * @param $from
  177. * @param $to
  178. * @return array|\multitype
  179. */
  180. public function weeklyVisitTrend($from, $to)
  181. {
  182. try {
  183. $result = $this->app->data_cube->weeklyVisitTrend($from, $to);
  184. if (isset($result['errcode']) && $result['errcode'] != 0) {
  185. return $this->error([], $result["errmsg"]);
  186. }
  187. return $this->success($result["list"]);
  188. } catch (\Exception $e) {
  189. return $this->error([], $e->getMessage());
  190. }
  191. }
  192. /**
  193. * 访问月趋势
  194. * @param $from
  195. * @param $to
  196. * @return array|\multitype
  197. */
  198. public function monthlyVisitTrend($from, $to)
  199. {
  200. try {
  201. $result = $this->app->data_cube->monthlyVisitTrend($from, $to);
  202. if (isset($result['errcode']) && $result['errcode'] != 0) {
  203. return $this->error([], $result["errmsg"]);
  204. }
  205. return $this->success($result["list"]);
  206. } catch (\Exception $e) {
  207. return $this->error([], $e->getMessage());
  208. }
  209. }
  210. /**
  211. * 访问分布
  212. * @param $from
  213. * @param $to
  214. */
  215. public function visitDistribution($from, $to)
  216. {
  217. try {
  218. $result = $this->app->data_cube->visitDistribution($from, $to);
  219. if (isset($result['errcode']) && $result['errcode'] != 0) {
  220. return $this->error($result, $result["errmsg"]);
  221. }
  222. return $this->success($result["list"]);
  223. } catch (\Exception $e) {
  224. return $this->error([], $e->getMessage());
  225. }
  226. }
  227. /**
  228. * 访问页面
  229. * @param $from
  230. * @param $to
  231. */
  232. public function visitPage($from, $to)
  233. {
  234. try {
  235. $result = $this->app->data_cube->visitPage($from, $to);
  236. if (isset($result['errcode']) && $result['errcode'] != 0) {
  237. return $this->error([], $result["errmsg"]);
  238. }
  239. return $this->success($result["list"]);
  240. } catch (\Exception $e) {
  241. return $this->error([], $e->getMessage());
  242. }
  243. }
  244. /************************************************************* 数据统计与分析 end **************************************************************/
  245. /**
  246. * 下载小程序代码包
  247. * @param $site_id
  248. */
  249. public function download($site_id)
  250. {
  251. $source_file_path = $this->createTempPackage($site_id, 'public/weapp');
  252. $file_arr = getFileMap($source_file_path);
  253. if (!empty($file_arr)) {
  254. $zipname = 'upload/weapp_' . $site_id . '_' . date('Ymd') . '.zip';
  255. $zip = new \ZipArchive();
  256. $res = $zip->open($zipname, \ZipArchive::CREATE);
  257. if ($res === TRUE) {
  258. foreach ($file_arr as $file_path => $file_name) {
  259. if (is_dir($file_path)) {
  260. $file_path = str_replace($source_file_path . '/', '', $file_path);
  261. $zip->addEmptyDir($file_path);
  262. } else {
  263. $zip_path = str_replace($source_file_path . '/', '', $file_path);
  264. $zip->addFile($file_path, $zip_path);
  265. }
  266. }
  267. $zip->close();
  268. header("Content-Type: application/zip");
  269. header("Content-Transfer-Encoding: Binary");
  270. header("Content-Length: " . filesize($zipname));
  271. header("Content-Disposition: attachment; filename=\"" . basename($zipname) . "\"");
  272. readfile($zipname);
  273. @unlink($zipname);
  274. deleteDir($source_file_path);
  275. }
  276. }
  277. }
  278. /**
  279. * 创建临时包
  280. * @param $site_id
  281. * @param $package_path
  282. * @param string $to_path
  283. * @return array
  284. */
  285. private function createTempPackage($site_id, $package_path, $to_path = '')
  286. {
  287. if (is_dir($package_path)) {
  288. $package = scandir($package_path);
  289. if (empty($to_path)) {
  290. $to_path = 'upload/temp/' . $site_id . '/';
  291. dir_mkdir($to_path);
  292. }
  293. foreach ($package as $path) {
  294. $temp_path = $package_path . '/' . $path;
  295. if (is_dir($temp_path)) {
  296. if ($path == '.' || $path == '..') {//判断是否为系统隐藏的文件.和.. 如果是则跳过否则就继续往下走,防止无限循环再这里。
  297. continue;
  298. }
  299. dir_mkdir($to_path . $path);
  300. $this->createTempPackage($site_id, $temp_path, $to_path . $path . '/');
  301. } else {
  302. if (file_exists($temp_path)) {
  303. copy($temp_path, $to_path . $path);
  304. if (stristr($temp_path, 'common/vendor.js')) {
  305. $content = file_get_contents($to_path . $path);
  306. $content = $this->paramReplace($site_id, $content);
  307. file_put_contents($to_path . $path, $content);
  308. }
  309. }
  310. }
  311. }
  312. return $to_path;
  313. }
  314. }
  315. /**
  316. * 参数替换
  317. * @param $site_id
  318. * @param $string
  319. * @return null|string|string[]
  320. */
  321. private function paramReplace($site_id, $string)
  322. {
  323. $api_model = new Api();
  324. $api_config = $api_model->getApiConfig();
  325. $api_config = $api_config['data'];
  326. $web_config_model = new WebConfig();
  327. $web_config = $web_config_model ->getMapConfig();
  328. $web_config = $web_config['data']['value'];
  329. $socket_url = (strstr(ROOT_URL, 'https://') === false ? str_replace('http', 'ws', ROOT_URL) : str_replace('https', 'wss', ROOT_URL)) . '/wss';
  330. $patterns = [
  331. '/\{\{\$baseUrl\}\}/',
  332. '/\{\{\$imgDomain\}\}/',
  333. '/\{\{\$h5Domain\}\}/',
  334. '/\{\{\$mpKey\}\}/',
  335. '/\{\{\$apiSecurity\}\}/',
  336. '/\{\{\$publicKey\}\}/',
  337. '/\{\{\$webSocket\}\}/'
  338. ];
  339. $replacements = [
  340. ROOT_URL,
  341. ROOT_URL,
  342. ROOT_URL . '/h5',
  343. $web_config['tencent_map_key'] ?? '',
  344. $api_config['is_use'] ?? 0,
  345. $api_config['value']['public_key'] ?? '',
  346. $socket_url
  347. ];
  348. $string = preg_replace($patterns, $replacements, $string);
  349. return $string;
  350. }
  351. /**
  352. * 消息解密
  353. * @param array $param
  354. */
  355. public function decryptData($param = [])
  356. {
  357. try {
  358. $cache = Cache::get('weapp_' . $param['weapp_openid']);
  359. $session_key = $cache['session_key'] ?? '';
  360. $result = $this->app->encryptor->decryptData($session_key, $param['iv'], $param['encryptedData']);
  361. if (isset($result['errcode']) && $result['errcode'] != 0) {
  362. return $this->handleError($result['errcode'], $result['errmsg']);
  363. }
  364. return $this->success($result);
  365. } catch (\Exception $e) {
  366. if (property_exists($e, 'formattedResponse')) {
  367. return $this->handleError($e->formattedResponse['errcode'], $e->formattedResponse['errmsg']);
  368. }
  369. return $this->error([], $e->getMessage());
  370. }
  371. }
  372. /**
  373. * 获取用户手机号
  374. * @param $code
  375. * @return array
  376. * @throws \GuzzleHttp\Exception\GuzzleException
  377. */
  378. public function getUserPhoneNumber($code){
  379. try {
  380. $result = $this->app->auth->phoneNumber($code);
  381. if (isset($result['errcode']) && $result['errcode'] != 0) {
  382. return $this->handleError($result['errcode'], $result['errmsg']);
  383. }
  384. return $this->success($result['phone_info']);
  385. } catch (\Exception $e) {
  386. if (property_exists($e, 'formattedResponse')) {
  387. return $this->handleError($e->formattedResponse['errcode'], $e->formattedResponse['errmsg']);
  388. }
  389. return $this->error([], $e->getMessage());
  390. }
  391. }
  392. /**
  393. * 获取订阅消息template_id
  394. * @param array $param
  395. */
  396. public function getTemplateId(array $param){
  397. try {
  398. $result = $this->app->subscribe_message->addTemplate($param['tid'], $param['kidList'], $param['sceneDesc']);
  399. return $result;
  400. } catch (\Exception $e) {
  401. return ['errcode' => -1, 'errmsg' => $e->getMessage()];
  402. }
  403. }
  404. /**
  405. * 发送订阅消息
  406. * @param array $param
  407. * @return array
  408. */
  409. public function sendTemplateMessage(array $param){
  410. $result = $this->app->subscribe_message->send([
  411. 'template_id' => $param['template_id'],// 模板id
  412. 'touser' => $param['openid'], // openid
  413. 'page' => $param['page'], // 点击模板卡片后的跳转页面 支持带参数
  414. 'data' => $param['data'] // 模板变量
  415. ]);
  416. if (isset($result['errcode']) && $result['errcode'] != 0) {
  417. return $this->error($result, $result["errmsg"]);
  418. }
  419. return $this->success($result);
  420. }
  421. /**
  422. * 消息推送
  423. */
  424. public function relateWeixin(){
  425. $server = $this->app->server;
  426. $message = $server->getMessage();
  427. if (isset($message['MsgType'])) {
  428. switch ($message['MsgType']) {
  429. case 'event':
  430. $this->app->server->push(function ($res) {
  431. // 商品审核结果通知
  432. if ($res['Event'] == 'open_product_spu_audit' && addon_is_exit('shopcomponent', $this->site_id)) {
  433. model('shopcompoent_goods')->update([
  434. 'edit_status' => $res['OpenProductSpuAudit']['status'],
  435. 'reject_reason' => !empty($res['OpenProductSpuAudit']['reject_reason']) ?: '',
  436. 'audit_time' => time()
  437. ], [
  438. ['out_product_id', '=', $res['OpenProductSpuAudit']['out_product_id'] ]
  439. ]);
  440. }
  441. // 类目审核结果通知
  442. if ($res['Event'] == 'open_product_category_audit' && addon_is_exit('shopcomponent', $this->site_id)) {
  443. model('shopcompoent_category_audit')->update([
  444. 'status' => $res['QualificationAuditResult']['status'],
  445. 'reject_reason' => !empty($res['QualificationAuditResult']['reject_reason']) ?: '',
  446. 'audit_time' => time()
  447. ], [
  448. ['audit_id', '=', $res['QualificationAuditResult']['audit_id'] ]
  449. ]);
  450. }
  451. // 视频号支付订单回调
  452. if ($res['Event'] == 'open_product_order_pay' && addon_is_exit('shopcomponent', $this->site_id)) {
  453. event("shopcomponentNotify", $res);
  454. }
  455. });
  456. break;
  457. }
  458. }
  459. $response = $this->app->server->serve();
  460. return $response->send();
  461. }
  462. /**
  463. * 检查场景值是否在支付校验范围内
  464. * @param $scene
  465. * @return array
  466. * @throws \GuzzleHttp\Exception\GuzzleException
  467. */
  468. public function sceneCheck($scene){
  469. try {
  470. $result = $this->app->mini_store->checkScene($scene);
  471. if (isset($result['errcode']) && $result['errcode'] == 0) {
  472. return $this->success($result['is_matched']);
  473. } else {
  474. return $this->error('', $result['errmsg']);
  475. }
  476. } catch (\Exception $e) {
  477. return $this->error([], $e->getMessage());
  478. }
  479. }
  480. public function createOrder($order_info){
  481. try {
  482. $result = $this->app->mini_store->addOrder($order_info);
  483. if (isset($result['errcode']) && $result['errcode'] == 0) {
  484. return $this->success($result['is_matched']);
  485. } else {
  486. return $this->error('', $result['errmsg']);
  487. }
  488. } catch (\Exception $e) {
  489. return $this->error([], $e->getMessage());
  490. }
  491. }
  492. /**
  493. * 处理错误信息
  494. * @param $errcode
  495. * @param string $message
  496. * @return array
  497. */
  498. public function handleError($errcode, $message = ''){
  499. $error = require 'addon/weapp/config/weapp_error.php';
  500. return $this->error([], $error[ $errcode ] ?? $message);
  501. }
  502. }