V2BaseController.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <?php
  2. /**
  3. * V2BaseController.php UTF-8
  4. * V2版本基类
  5. *
  6. * @date : 2018/1/15 21:35
  7. *
  8. * @license 这不是一个自由软件,未经授权不许任何使用和传播。
  9. * @author : wuyonghong <wyh@huosdk.com>
  10. * @version : HUOSDK 8.0
  11. */
  12. namespace wap\common\controller;
  13. use huo\controller\agent\Agent;
  14. use huo\controller\common\HuoSession;
  15. use huolib\constant\CommonConst;
  16. use huolib\constant\FormatConst;
  17. use huolib\status\CommonStatus;
  18. use think\Config;
  19. use think\Controller;
  20. use think\Db;
  21. use think\exception\HttpResponseException;
  22. use think\exception\ValidateException;
  23. use think\Loader;
  24. use think\Log;
  25. use think\Response;
  26. use think\View;
  27. class V2BaseController extends Controller {
  28. // 语言
  29. protected $lang = 'en';
  30. /* 返回类型 */
  31. protected $response_type = 'html';
  32. protected $allowed_device_types = ['mobile', 'android', 'iphone', 'ipad', 'web', 'pc', 'mac', 'wxapp'];
  33. /**
  34. * @var \think\Request Request实例
  35. */
  36. protected $request;
  37. protected $agent_site; /* 渠道专属链接 */
  38. // 验证失败是否抛出异常
  39. protected $fail_exception = false;
  40. // 是否批量验证
  41. protected $batch_validate = false;
  42. protected $rq_data = [];
  43. protected $sess_config
  44. = [
  45. 'id' => '',
  46. 'prefix' => '',
  47. 'type' => '',
  48. 'expire' => CommonConst::CONST_DAY_SECONDS
  49. ];
  50. /**
  51. * 前置操作方法列表
  52. *
  53. * @var array $before_action_list
  54. * @access protected
  55. */
  56. protected $before_action_list = [];
  57. // 初始化
  58. private function _initLang() {
  59. $_lang = $this->request->header('HS-Lang/s', 'en-us');
  60. $this->lang = $_lang;
  61. config('default_lang', $this->lang);
  62. }
  63. protected function _initialize() {
  64. if(!$this->request->isMobile()) {
  65. $this->redirect(WEBSITE);
  66. }
  67. $this->response_type = $this->request->param('format/s', FormatConst::FORMAT_HTML);
  68. if (FormatConst::FORMAT_HTML == $this->response_type) {
  69. $this->_initializeView();
  70. $_site = $this->request->domain();
  71. $_agent_id = (new Agent())->getAgentIdBySite($_site);
  72. if (empty($_agent_id)) {
  73. $_agent_id = $this->request->request('agent_id', 0);
  74. }
  75. if (empty($_agent_id)) {
  76. $_agent_id = (new HuoSession($this->mem_id))->getAgentId();
  77. } else {
  78. (new HuoSession($this->mem_id))->setAgentId($_agent_id);
  79. }
  80. $siteInfo = cmf_get_site_info($_agent_id);
  81. View::share('site_info', $siteInfo);
  82. View::share('agent_id', $_agent_id);
  83. View::share('agent_site', $_site);
  84. }
  85. Config::set('default_return_type', $this->response_type);
  86. $this->_initLang();
  87. $this->checkParam();
  88. /* 记录请求数据 */
  89. Log::write($this->request->getContent(), Log::LOG);
  90. Log::write($this->rq_data, Log::LOG);
  91. if ($this->request->isOptions()) {
  92. $this->success();
  93. }
  94. }
  95. /**
  96. * 前置操作
  97. *
  98. * @access protected
  99. *
  100. * @param string $method 前置操作方法名
  101. * @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]]
  102. */
  103. protected function beforeAction($method, $options = []) {
  104. if (isset($options['only'])) {
  105. if (is_string($options['only'])) {
  106. $options['only'] = explode(',', $options['only']);
  107. }
  108. if (!in_array($this->request->action(), $options['only'])) {
  109. return;
  110. }
  111. } elseif (isset($options['except'])) {
  112. if (is_string($options['except'])) {
  113. $options['except'] = explode(',', $options['except']);
  114. }
  115. if (in_array($this->request->action(), $options['except'])) {
  116. return;
  117. }
  118. }
  119. call_user_func([$this, $method]);
  120. }
  121. /**
  122. * 返回数据
  123. *
  124. * @param array $data
  125. */
  126. protected function returnData($data = []) {
  127. $_msg = $data['msg'];
  128. $_code = $data['code'];
  129. $_data = $data['data'];
  130. if (CommonStatus::NO_ERROR != $_code) {
  131. $this->error($_msg, $_data, $_code);
  132. }
  133. $this->success($_msg, $_data, $_code);
  134. }
  135. /**
  136. * 操作错误跳转的快捷方法
  137. *
  138. * @access protected
  139. *
  140. * @param mixed $msg 提示信息,若要指定错误码,可以传数组,格式为['code'=>您的错误码,'msg'=>'您的错误消息']
  141. * @param mixed $data 返回的数据
  142. * @param int $code
  143. *
  144. * @param array $header 发送的Header信息
  145. *
  146. * @param null $url
  147. * @param int $wait
  148. *
  149. * @return void
  150. */
  151. protected function error($msg = '', $data = '', $code = 400, array $header = [], $url = null, $wait = 3) {
  152. $type = $this->getResponseType();
  153. if ('html' == $type) {
  154. parent::error($msg, $data, $code, $header, $url, $wait);
  155. }
  156. if (empty($data)) {
  157. $data = null;
  158. }
  159. $result = [
  160. 'code' => $code,
  161. 'msg' => $msg,
  162. 'data' => $data,
  163. ];
  164. $header['Access-Control-Allow-Origin'] = '*';
  165. $header['Access-Control-Allow-Headers'] = 'X-Requested-With,Content-Type,HS-Device-Type,HS-Token,HS-Lang';
  166. $header['Access-Control-Allow-Methods'] = 'GET,POST,PATCH,PUT,DELETE,OPTIONS';
  167. $response = Response::create($result, $type)->header($header);
  168. throw new HttpResponseException($response);
  169. }
  170. /**
  171. * 获取当前的response 输出类型
  172. *
  173. * @access protected
  174. * @return string
  175. */
  176. protected function getResponseType() {
  177. return $this->response_type;
  178. }
  179. /**
  180. * 获取当前登录用户的id
  181. *
  182. * @return int
  183. */
  184. public function getMemId() {
  185. if (empty($this->mem_id)) {
  186. $this->error(lang('NO_LOGIN'), '', 1002);
  187. }
  188. // $this->mem_id = rand(1, 10);
  189. return $this->mem_id;
  190. }
  191. /**
  192. * 设置验证失败后是否抛出异常
  193. *
  194. * @access protected
  195. *
  196. * @param bool $fail 是否抛出异常
  197. *
  198. * @return $this
  199. */
  200. protected function validateFailException($fail = true) {
  201. $this->fail_exception = $fail;
  202. return $this;
  203. }
  204. /**
  205. * 验证数据
  206. *
  207. * @access protected
  208. *
  209. * @param array $data 数据
  210. * @param string|array $validate 验证器名或者验证规则数组
  211. * @param array $message 提示信息
  212. * @param bool $batch 是否批量验证
  213. * @param mixed $callback 回调方法(闭包)
  214. *
  215. * @return array|string|true
  216. * @throws ValidateException
  217. */
  218. protected function validate($data, $validate, $message = [], $batch = false, $callback = null) {
  219. if (is_array($validate)) {
  220. $_valid_class = Loader::validate();
  221. $_valid_class->rule($validate);
  222. } else {
  223. if (strpos($validate, '.')) {
  224. // 支持场景
  225. list($validate, $scene) = explode('.', $validate);
  226. }
  227. $_valid_class = Loader::validate($validate);
  228. if (!empty($scene)) {
  229. $_valid_class->scene($scene);
  230. }
  231. }
  232. // 是否批量验证
  233. if ($batch || $this->batch_validate) {
  234. $_valid_class->batch(true);
  235. }
  236. if (is_array($message)) {
  237. $_valid_class->message($message);
  238. }
  239. if ($callback && is_callable($callback)) {
  240. call_user_func_array($callback, [$_valid_class, &$data]);
  241. }
  242. if (!$_valid_class->check($data)) {
  243. if ($this->fail_exception) {
  244. throw new ValidateException($_valid_class->getError());
  245. } else {
  246. return $_valid_class->getError();
  247. }
  248. } else {
  249. return true;
  250. }
  251. }
  252. /**
  253. * 操作成功跳转的快捷方法
  254. *
  255. * @access protected
  256. *
  257. * @param mixed $msg 提示信息
  258. * @param mixed $data 返回的数据
  259. * @param array $header 发送的Header信息
  260. *
  261. * @param int $code 返回码
  262. *
  263. * @return void
  264. */
  265. protected function success($msg = '', $data = '', $code = 200, array $header = [], $url = null, $wait = 3) {
  266. $type = $this->getResponseType();
  267. if ('html' == $type) {
  268. parent::success($msg, $data, $code, $header, $url, $wait);
  269. }
  270. if (empty($data)) {
  271. $data = null;
  272. }
  273. $result = [
  274. 'code' => $code,
  275. 'msg' => $msg,
  276. 'data' => $data,
  277. ];
  278. $header['Access-Control-Allow-Origin'] = '*';
  279. $header['Access-Control-Allow-Headers'] = 'X-Requested-With,Content-Type,HS-Device-Type,HS-Token,HS-Lang';
  280. $header['Access-Control-Allow-Methods'] = 'GET,POST,PATCH,PUT,DELETE,OPTIONS';
  281. $response = Response::create($result, $type)->header($header);
  282. throw new HttpResponseException($response);
  283. }
  284. protected function checkParam() {
  285. $_is_json = false;
  286. // $_params = $this->request->post();
  287. $_params = $_REQUEST;
  288. if ($this->request->isPost()) {
  289. // $_params = $_POST;
  290. }
  291. $this->rq_data = $this->getParam($_params, $_is_json);
  292. if (empty($this->rq_data)) {
  293. $this->rq_data = [];
  294. }
  295. $this->checkTime();
  296. }
  297. /**
  298. * 获取请求参数
  299. *
  300. * @param mixed $param
  301. * @param bool $is_json
  302. *
  303. * @return bool|mixed|string
  304. */
  305. public function getParam($param, $is_json = false) {
  306. if (empty($param)) {
  307. return '';
  308. }
  309. if ($is_json) {
  310. $_param = json_decode($param, true);
  311. if (JSON_ERROR_NONE != json_last_error()) {
  312. return false;
  313. }
  314. } else {
  315. foreach ($param as $_k => $_v) {
  316. if (strpos($_k, '-')) {
  317. list($_k1, $_k2) = explode('-', $_k);
  318. if (is_array($_k2)) {
  319. return false;
  320. }
  321. $_param[$_k1][$_k2] = $_v;
  322. } else {
  323. $_param[$_k] = $_v;
  324. }
  325. }
  326. }
  327. if (empty($_param)) {
  328. return false;
  329. }
  330. return $_param;
  331. }
  332. /**
  333. * 校验请求事件
  334. */
  335. protected function checkTime() {
  336. $_ts = get_val($this->rq_data, 'ts', 0);
  337. if (abs($_ts - time()) < 5) {
  338. $this->error('time is error');
  339. }
  340. }
  341. public function _initializeView() {
  342. $cmfThemePath = config('cmf_theme_path');
  343. $cmfDefaultTheme = cmf_get_current_theme();
  344. $themePath = "{$cmfThemePath}{$cmfDefaultTheme}";
  345. $root = cmf_get_root();
  346. //使cdn设置生效
  347. $cdnSettings = cmf_get_option('cdn_settings');
  348. if (empty($cdnSettings['cdn_static_root'])) {
  349. $viewReplaceStr = [
  350. '__ROOT__' => $root,
  351. '__TMPL__' => "{$root}/{$themePath}",
  352. '__STATIC__' => "{$root}/static",
  353. '__WEB_ROOT__' => $root
  354. ];
  355. } else {
  356. $cdnStaticRoot = rtrim($cdnSettings['cdn_static_root'], '/');
  357. $viewReplaceStr = [
  358. '__ROOT__' => $root,
  359. '__TMPL__' => "{$cdnStaticRoot}/{$themePath}",
  360. '__STATIC__' => "{$cdnStaticRoot}/static",
  361. '__WEB_ROOT__' => $cdnStaticRoot
  362. ];
  363. }
  364. $viewReplaceStr = array_merge(config('view_replace_str'), $viewReplaceStr);
  365. config('template.view_base', "{$themePath}/");
  366. config('view_replace_str', $viewReplaceStr);
  367. $themeErrorTmpl = "{$themePath}/error.html";
  368. if (file_exists_case($themeErrorTmpl)) {
  369. config('dispatch_error_tmpl', $themeErrorTmpl);
  370. }
  371. $themeSuccessTmpl = "{$themePath}/success.html";
  372. if (file_exists_case($themeSuccessTmpl)) {
  373. config('dispatch_success_tmpl', $themeSuccessTmpl);
  374. }
  375. }
  376. /**
  377. * 加载模板输出
  378. *
  379. * @access protected
  380. *
  381. * @param string $template 模板文件名
  382. * @param array $vars 模板输出变量
  383. * @param array $replace 模板替换
  384. * @param array $config 模板参数
  385. *
  386. * @return mixed
  387. */
  388. protected function fetch($template = '', $vars = [], $replace = [], $config = []) {
  389. $template = $this->parseTemplate($template);
  390. $more = $this->getThemeFileMore($template);
  391. $this->assign('theme_vars', $more['vars']);
  392. $this->assign('theme_widgets', $more['widgets']);
  393. return parent::fetch($template, $vars, $replace, $config);
  394. }
  395. /**
  396. * 自动定位模板文件
  397. *
  398. * @access private
  399. *
  400. * @param string $template 模板文件规则
  401. *
  402. * @return string
  403. */
  404. private function parseTemplate($template) {
  405. // 分析模板文件规则
  406. $request = $this->request;
  407. // 获取视图根目录
  408. if (strpos($template, '@')) {
  409. // 跨模块调用
  410. list($module, $template) = explode('@', $template);
  411. }
  412. $viewBase = config('template.view_base');
  413. if ($viewBase) {
  414. // 基础视图目录
  415. $module = isset($module) ? $module : $request->module();
  416. $path = $viewBase.($module ? $module.DS : '');
  417. } else {
  418. $path = isset($module) ? APP_PATH.$module.DS.'view'.DS : config('template.view_path');
  419. }
  420. $depr = config('template.view_depr');
  421. if (0 !== strpos($template, '/')) {
  422. $template = str_replace(['/', ':'], $depr, $template);
  423. $controller = cmf_parse_name($request->controller());
  424. if ($controller) {
  425. if ('' == $template) {
  426. // 如果模板文件名为空 按照默认规则定位
  427. $template = str_replace('.', DS, $controller).$depr.$request->action();
  428. } elseif (false === strpos($template, $depr)) {
  429. $template = str_replace('.', DS, $controller).$depr.$template;
  430. }
  431. }
  432. } else {
  433. $template = str_replace(['/', ':'], $depr, substr($template, 1));
  434. }
  435. return $path.ltrim($template, '/').'.'.ltrim(config('template.view_suffix'), '.');
  436. }
  437. /**
  438. * 获取模板文件变量
  439. *
  440. * @param string $file
  441. * @param string $theme
  442. *
  443. * @return array
  444. */
  445. private function getThemeFileMore($file, $theme = "") {
  446. //TODO 增加缓存
  447. $theme = empty($theme) ? cmf_get_current_theme() : $theme;
  448. $themePath = config('cmf_theme_path');
  449. $file = str_replace('\\', '/', $file);
  450. $file = str_replace('//', '/', $file);
  451. $file = str_replace(['.html', '.php', $themePath.$theme."/"], '', $file);
  452. $files = Db::name('theme_file')->field('more')->where(['theme' => $theme])->where(
  453. function ($query) use ($file) {
  454. $query->where(['is_public' => 1])->whereOr(['file' => $file]);
  455. }
  456. )->select();
  457. $vars = [];
  458. $widgets = [];
  459. foreach ($files as $file) {
  460. $oldMore = json_decode($file['more'], true);
  461. if (!empty($oldMore['vars'])) {
  462. foreach ($oldMore['vars'] as $varName => $var) {
  463. $vars[$varName] = $var['value'];
  464. }
  465. }
  466. if (!empty($oldMore['widgets'])) {
  467. foreach ($oldMore['widgets'] as $widgetName => $widget) {
  468. $widgetVars = [];
  469. if (!empty($widget['vars'])) {
  470. foreach ($widget['vars'] as $varName => $var) {
  471. $widgetVars[$varName] = $var['value'];
  472. }
  473. }
  474. $widget['vars'] = $widgetVars;
  475. $widgets[$widgetName] = $widget;
  476. }
  477. }
  478. }
  479. return ['vars' => $vars, 'widgets' => $widgets];
  480. }
  481. }