RoutineTemplate.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2020 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\controller\admin\v1\wechat;
  12. use app\controller\admin\AuthController;
  13. use app\jobs\notice\template\RoutineTemplateJob;
  14. use app\services\message\SystemNotificationServices;
  15. use app\services\message\TemplateMessageServices;
  16. use app\services\other\QrcodeServices;
  17. use app\services\system\attachment\SystemAttachmentServices;
  18. use crmeb\exceptions\AdminException;
  19. use crmeb\services\{FileService, FormBuilder as Form, template\Template, UploadService, wechat\MiniProgram};
  20. use crmeb\services\CacheService;
  21. use think\exception\ValidateException;
  22. use think\facade\App;
  23. use think\facade\Route as Url;
  24. use think\Request;
  25. use function Swoole\Coroutine\batch;
  26. /**
  27. * Class RoutineTemplate
  28. * @package app\controller\admin\v1\application\routine
  29. */
  30. class RoutineTemplate extends AuthController
  31. {
  32. protected $cacheTag = 'system_routine';
  33. /**
  34. * 构造方法
  35. * WechatTemplate constructor.
  36. * @param App $app
  37. * @param TemplateMessageServices $services
  38. */
  39. public function __construct(App $app, TemplateMessageServices $services)
  40. {
  41. parent::__construct($app);
  42. $this->services = $services;
  43. }
  44. /**
  45. * 显示资源列表
  46. *
  47. * @return \think\Response
  48. */
  49. public function index()
  50. {
  51. $where = $this->request->getMore([
  52. ['name', ''],
  53. ['status', '']
  54. ]);
  55. $where['type'] = 0;
  56. $data = $this->services->getTemplateList($where);
  57. $industry = CacheService::get($this->cacheTag . '_wechat_industry', function () {
  58. try {
  59. $cache = (new Template('wechat'))->getIndustry();
  60. if (!$cache) return [];
  61. return $cache->toArray();
  62. } catch (\Exception $e) {
  63. return $e->getMessage();
  64. }
  65. }, 0) ?: [];
  66. !is_array($industry) && $industry = [];
  67. $industry['primary_industry'] = isset($industry['primary_industry']) ? $industry['primary_industry']['first_class'] . ' | ' . $industry['primary_industry']['second_class'] : '未选择';
  68. $industry['secondary_industry'] = isset($industry['secondary_industry']) ? $industry['secondary_industry']['first_class'] . ' | ' . $industry['secondary_industry']['second_class'] : '未选择';
  69. $lst = [
  70. 'industry' => $industry,
  71. 'count' => $data['count'],
  72. 'list' => $data['list']
  73. ];
  74. return $this->success($lst);
  75. }
  76. /**
  77. * 显示创建资源表单页.
  78. *
  79. * @return \think\Response
  80. */
  81. public function create()
  82. {
  83. $f = array();
  84. $f[] = Form::input('tempkey', '模板编号');
  85. $f[] = Form::input('tempid', '模板ID');
  86. $f[] = Form::input('name', '模板名');
  87. $f[] = Form::input('content', '回复内容')->type('textarea');
  88. $f[] = Form::radio('status', '状态', 1)->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
  89. return $this->makePostForm('添加模板消息', $f, Url::buildUrl('/app/routine'), 'POST');
  90. }
  91. /**
  92. * 保存新建的资源
  93. *
  94. * @param \think\Request $request
  95. * @return \think\Response
  96. */
  97. public function save(Request $request)
  98. {
  99. $data = $this->request->postMore([
  100. 'tempkey',
  101. 'tempid',
  102. 'name',
  103. 'content',
  104. ['status', 0]
  105. ]);
  106. if ($data['tempkey'] == '') return $this->fail('请输入模板编号');
  107. if ($data['tempkey'] != '' && $this->services->getOne(['tempkey' => $data['tempkey']]))
  108. return $this->fail('请输入模板编号已存在,请重新输入');
  109. if ($data['tempid'] == '') return $this->fail('请输入模板ID');
  110. if ($data['name'] == '') return $this->fail('请输入模板名');
  111. if ($data['content'] == '') return $this->fail('请输入回复内容');
  112. $data['add_time'] = time();
  113. $this->services->save($data);
  114. return $this->success('添加模板消息成功!');
  115. }
  116. /**
  117. * 显示指定的资源
  118. *
  119. * @param int $id
  120. * @return \think\Response
  121. */
  122. public function read($id)
  123. {
  124. //
  125. }
  126. /**
  127. * 显示编辑资源表单页.
  128. *
  129. * @param int $id
  130. * @return \think\Response
  131. */
  132. public function edit($id)
  133. {
  134. if (!$id) return $this->fail('数据不存在');
  135. $product = $this->services->get($id);
  136. if (!$product) return $this->fail('数据不存在!');
  137. $f = array();
  138. $f[] = Form::input('tempkey', '模板编号', $product->getData('tempkey'))->disabled(1);
  139. $f[] = Form::input('name', '模板名', $product->getData('name'))->disabled(1);
  140. $f[] = Form::input('tempid', '模板ID', $product->getData('tempid'));
  141. $f[] = Form::radio('status', '状态', $product->getData('status'))->options([['label' => '开启', 'value' => 1], ['label' => '关闭', 'value' => 0]]);
  142. return $this->makePostForm('编辑模板消息', $f, Url::buildUrl('/app/routine/' . $id), 'PUT');
  143. }
  144. /**
  145. * 保存更新的资源
  146. *
  147. * @param \think\Request $request
  148. * @param int $id
  149. * @return \think\Response
  150. */
  151. public function update(Request $request, $id)
  152. {
  153. $data = $this->request->postMore([
  154. 'tempid',
  155. ['status', 0]
  156. ]);
  157. if ($data['tempid'] == '') return $this->fail('请输入模板ID');
  158. if (!$id) return $this->fail('数据不存在');
  159. $product = $this->services->get($id);
  160. if (!$product) return $this->fail('数据不存在!');
  161. $this->services->update($id, $data, 'id');
  162. return $this->success('修改成功!');
  163. }
  164. /**
  165. * 删除指定资源
  166. *
  167. * @param int $id
  168. * @return \think\Response
  169. */
  170. public function delete($id)
  171. {
  172. if (!$id) return $this->fail('数据不存在!');
  173. if (!$this->services->delete($id))
  174. return $this->fail('删除失败,请稍候再试!');
  175. else
  176. return $this->success('删除成功!');
  177. }
  178. /**
  179. * 修改状态
  180. * @param $id
  181. * @param $status
  182. * @return mixed
  183. */
  184. public function set_status($id, $status)
  185. {
  186. if ($status == '' || $id == 0) return $this->fail('参数错误');
  187. $this->services->update($id, ['status' => $status], 'id');
  188. return $this->success($status == 0 ? '关闭成功' : '开启成功');
  189. }
  190. /**
  191. * 同步订阅消息
  192. * @return mixed
  193. * @throws \think\db\exception\DataNotFoundException
  194. * @throws \think\db\exception\DbException
  195. * @throws \think\db\exception\ModelNotFoundException
  196. */
  197. public function syncSubscribe()
  198. {
  199. if (!sys_config('routine_appId') || !sys_config('routine_appsecret')) {
  200. throw new AdminException('请先配置小程序appid、appSecret等参数');
  201. }
  202. $all = $this->services->getTemplateList(['status' => 1, 'type' => 0]);
  203. $errMessage = [
  204. '-1' => '系统繁忙,此时请稍候再试',
  205. '40001' => 'AppSecret错误或者AppSecret不属于这个小程序,请确认AppSecret 的正确性',
  206. '40002' => '请确保grant_type字段值为client_credential',
  207. '40013' => '不合法的AppID,请检查AppID的正确性,避免异常字符,注意大小写',
  208. '40125' => '小程序配置无效,请检查配置',
  209. '41002' => '缺少appid参数',
  210. '41004' => '缺少secret参数',
  211. '43104' => 'appid与openid不匹配',
  212. '45009' => '达到微信api每日限额上限',
  213. '200011' => '此账号已被封禁,无法操作',
  214. '200012' => '个人模版数已达上限,上限25个',
  215. ];
  216. if ($all['list']) {
  217. $i = 0;
  218. foreach ($all['list'] as $template) {
  219. if (!$template['tempkey']) {
  220. continue;
  221. }
  222. if (!isset($template['kid'])) {
  223. return $this->fail('数据库模版表(template_message)缺少字段:kid');
  224. }
  225. if (isset($template['kid']) && $template['kid']) {
  226. continue;
  227. }
  228. RoutineTemplateJob::dispatch([$template, $errMessage]);
  229. $i++;
  230. }
  231. /** @var SystemNotificationServices $systemNotificationServices */
  232. $systemNotificationServices = app()->make(SystemNotificationServices::class);
  233. //清空模版缓存
  234. $systemNotificationServices->clearTemplateCache();
  235. }
  236. return $this->success($i ? '队列执行中,请稍后查看,小程序需要选择:生活服务/百货/超市/便利店类目' : '已同步成功,请不要重复同步');
  237. }
  238. /**
  239. * 下载小程序
  240. * @return mixed
  241. */
  242. public function downloadTemp()
  243. {
  244. [$name, $is_live] = $this->request->postMore([
  245. ['name', ''],
  246. ['is_live', 0]
  247. ], true);
  248. if (!sys_config('routine_appId', '')) {
  249. throw new AdminException('请先在设置->系统设置->应用设置,配置小程序相关信息');
  250. }
  251. if (!file_exists(public_path() . 'statics/mp_view') || count(scandir(public_path() . 'statics/mp_view')) == 2) {
  252. throw new AdminException('请先上传小程序打包文件,路径:' . public_path() . 'statics/mp_view');
  253. }
  254. try {
  255. @unlink(public_path() . 'statics/download/routine.zip');
  256. //拷贝源文件
  257. /** @var FileService $fileService */
  258. $fileService = app(FileService::class);
  259. $fileService->copyDir(public_path() . 'statics/mp_view', public_path() . 'statics/download');
  260. $baseUrl = sys_config('site_url');
  261. batch([
  262. 'appId' => function () use ($name) {
  263. try {
  264. $this->updateConfigJson(sys_config('routine_appId'), $name != '' ? $name : sys_config('routine_name'));
  265. } catch (\Throwable $e) {
  266. }
  267. return true;
  268. },
  269. 'updateApp' => function () use ($is_live) {
  270. $res1 = true;
  271. //是否开启直播
  272. if ($is_live == 0) {
  273. //pages.json 替换直播组件
  274. try {
  275. $this->updateAppJson();
  276. } catch (\Throwable $e) {
  277. }
  278. }
  279. return $res1;
  280. },
  281. 'updateProductDetail' => function () use ($is_live) {
  282. $res1 = true;
  283. //是否开启直播
  284. if ($is_live == 0) {
  285. //商品详情页 替换直播组件
  286. try {
  287. $this->updateProductDetailJson();
  288. } catch (\Throwable $e) {
  289. }
  290. }
  291. return $res1;
  292. },
  293. 'url' => function () use ($baseUrl) {
  294. //替换url
  295. try {
  296. $this->updateUrl($baseUrl);
  297. } catch (\Throwable $e) {
  298. }
  299. return true;
  300. }
  301. ]);
  302. //压缩文件
  303. $fileService->addZip(public_path() . 'statics/download', public_path() . 'statics/download/routine.zip', public_path() . 'statics/download');
  304. $data['url'] = $baseUrl . '/statics/download/routine.zip';
  305. return app('json')->success($data);
  306. } catch (\Throwable $e) {
  307. throw new AdminException($e->getMessage());
  308. }
  309. }
  310. /**
  311. * 替换url
  312. * @param $url
  313. */
  314. public function updateUrl($url)
  315. {
  316. $fileUrl = app()->getRootPath() . "public/statics/download/common/vendor.js";
  317. $string = file_get_contents($fileUrl); //加载配置文件
  318. $url = parse_url($url)['host'] ?? $url;
  319. $string = str_replace('demo.crmeb.com', $url, $string); // 正则查找然后替换
  320. $newFileUrl = app()->getRootPath() . "public/statics/download/common/vendor.js";
  321. @file_put_contents($newFileUrl, $string); // 写入配置文件
  322. }
  323. /**
  324. * 判断是否开启直播(弃用)
  325. * @param int $iszhibo
  326. */
  327. public function updateAppJson()
  328. {
  329. $fileUrl = app()->getRootPath() . "public/statics/download/app.json";
  330. $string = file_get_contents($fileUrl); //加载配置文件
  331. $pats = '/,
  332. "plugins": {
  333. "live-player-plugin": {
  334. "version": "(.*?)",
  335. "provider": "(.*?)"
  336. }
  337. }/';
  338. $string = preg_replace($pats, '', $string); // 正则查找然后替换
  339. $newFileUrl = app()->getRootPath() . "public/statics/download/app.json";
  340. @file_put_contents($newFileUrl, $string); // 写入配置文件
  341. }
  342. /**
  343. * 替换appid
  344. * @param string $appid
  345. * @param string $projectanme
  346. */
  347. public function updateConfigJson($appId = '', $projectName = '')
  348. {
  349. $fileUrl = app()->getRootPath() . "public/statics/download/project.config.json";
  350. $string = file_get_contents($fileUrl); //加载配置文件
  351. // 替换appid
  352. $appIdOld = '/"appid"(.*?),/';
  353. $appIdNew = '"appid"' . ': ' . '"' . $appId . '",';
  354. $string = preg_replace($appIdOld, $appIdNew, $string); // 正则查找然后替换
  355. // 替换小程序名称
  356. $projectNameOld = '/"projectname"(.*?),/';
  357. $projectNameNew = '"projectname"' . ': ' . '"' . $projectName . '",';
  358. $string = preg_replace($projectNameOld, $projectNameNew, $string); // 正则查找然后替换
  359. $newFileUrl = app()->getRootPath() . "public/statics/download/project.config.json";
  360. @file_put_contents($newFileUrl, $string); // 写入配置文件
  361. }
  362. /**
  363. * 替换商品详情直播
  364. */
  365. public function updateProductDetailJson()
  366. {
  367. $fileUrl = app()->getRootPath() . "public/statics/download/common/main.js";
  368. $string = file_get_contents($fileUrl); //加载配置文件
  369. $string = str_replace('requirePlugin("live-player-plugin")', 'null', $string);
  370. $pats = '/onShow:function\(e\)\{(.*?)},/';
  371. $string = preg_replace($pats, '', $string); // 正则查找然后替换
  372. $newFileUrl = app()->getRootPath() . "public/statics/download/common/main.js";
  373. @file_put_contents($newFileUrl, $string); // 写入配置文件
  374. }
  375. /**
  376. * 获取下载小程序模版页面数据
  377. * @return string
  378. * @throws \think\Exception
  379. * @throws \think\db\exception\DataNotFoundException
  380. * @throws \think\db\exception\DbException
  381. * @throws \think\db\exception\ModelNotFoundException
  382. */
  383. public function getDownloadInfo()
  384. {
  385. $data['routine_name'] = sys_config('routine_name', '');
  386. $appid = sys_config('routine_appId', '');
  387. if (!$appid) {
  388. throw new AdminException('请先在设置->系统设置->应用设置,配置小程序相关信息');
  389. }
  390. $name = $data['routine_name'] . '.jpg';
  391. /** @var SystemAttachmentServices $systemAttachmentModel */
  392. $systemAttachmentModel = app()->make(SystemAttachmentServices::class);
  393. $imageInfo = $systemAttachmentModel->getInfo(['name' => $name]);
  394. if (!$imageInfo) {
  395. /** @var QrcodeServices $qrcode */
  396. $qrcode = app()->make(QrcodeServices::class);
  397. $resForever = $qrcode->qrCodeForever(0, 'code');
  398. if ($resForever) {
  399. $data = 'id=' . $resForever->id . '&spid=0';
  400. $resCode = MiniProgram::appCodeUnlimit($data, '', 280);
  401. $res = ['res' => $resCode, 'id' => $resForever->id];
  402. } else {
  403. $res = false;
  404. }
  405. if (!$res) throw new ValidateException('二维码生成失败');
  406. $upload = UploadService::init(1);
  407. if ($upload->to('routine/code')->stream((string)$res['res'], $name) === false) {
  408. return $upload->getError();
  409. }
  410. $imageInfo = $upload->getUploadInfo();
  411. $imageInfo['image_type'] = 1;
  412. $systemAttachmentModel->attachmentAdd($imageInfo['name'], $imageInfo['size'], $imageInfo['type'], $imageInfo['dir'], $imageInfo['thumb_path'], 1, $imageInfo['image_type'], $imageInfo['time'], 2);
  413. $qrcode->update($res['id'], ['status' => 1, 'time' => time(), 'qrcode_url' => $imageInfo['dir']]);
  414. $data['code'] = sys_config('site_url') . $imageInfo['dir'];
  415. } else $data['code'] = sys_config('site_url') . $imageInfo['att_dir'];
  416. $data['appId'] = $appid;
  417. $data['help'] = 'https://doc.crmeb.com/web/pro/crmebprov2/1192';
  418. return $this->success($data);
  419. }
  420. }