Backend.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. <?php
  2. namespace app\common\controller;
  3. use app\admin\library\Auth;
  4. use app\common\library\Ip;
  5. use app\common\library\Ua;
  6. use app\common\utility\WhiteList;
  7. use app\main\constants\ErrorCodeConstants;
  8. use app\main\service\AdminService;
  9. use think\Config;
  10. use think\Controller;
  11. use think\Cookie;
  12. use think\Hook;
  13. use think\Lang;
  14. use think\Log;
  15. use think\Request;
  16. use think\Session;
  17. /**
  18. * 后台控制器基类
  19. */
  20. class Backend extends Controller
  21. {
  22. /**
  23. * 无需登录的方法,同时也就不需要鉴权了
  24. * @var array
  25. */
  26. protected $noNeedLogin = ['wxre', 'wxcallback','updateauth', 'autoviptoqdslogin'];
  27. /**
  28. * 无需鉴权的方法,但需要登录
  29. * @var array
  30. */
  31. protected $noNeedRight = ['recollect','export','ordersexport', 'getpalmsubaccountlist'];
  32. /**
  33. * 布局模板
  34. * @var string
  35. */
  36. protected $layout = 'default';
  37. /**
  38. * 权限控制类
  39. * @var Auth
  40. */
  41. protected $auth = null;
  42. /**
  43. * 快速搜索时执行查找的字段
  44. */
  45. protected $searchFields = 'id';
  46. /**
  47. * 是否是关联查询
  48. */
  49. protected $relationSearch = false;
  50. /**
  51. * 是否开启数据限制
  52. * 支持auth/personal
  53. * 表示按权限判断/仅限个人
  54. * 默认为禁用,若启用请务必保证表中存在admin_id字段
  55. */
  56. protected $dataLimit = false;
  57. /**
  58. * 数据限制字段
  59. */
  60. protected $dataLimitField = 'admin_id';
  61. /**
  62. * 是否开启Validate验证
  63. */
  64. protected $modelValidate = false;
  65. /**
  66. * 是否开启模型场景验证
  67. */
  68. protected $modelSceneValidate = false;
  69. /**
  70. * Multi方法可批量修改的字段
  71. */
  72. protected $multiFields = 'status';
  73. /**
  74. * 导入文件首行类型
  75. * 支持comment/name
  76. * 表示注释或字段名
  77. */
  78. protected $importHeadType = 'comment';
  79. /**
  80. * 当前登录用户的用户组id 超管1 普管2 渠道商3 代理商4
  81. */
  82. protected $group = null;
  83. /**
  84. * @var int 当前请求时间戳
  85. */
  86. protected $time = null;
  87. /**
  88. * 当前页面是否是微信三方平台授权页面
  89. * @var null
  90. */
  91. protected $platformAuthPage = false;
  92. /**
  93. * 引入后台控制器的traits
  94. */
  95. use \app\admin\library\traits\Backend;
  96. public function _initialize()
  97. {
  98. $this->time = $this->request->server('REQUEST_TIME');
  99. //config('site.url_agent', 'm{0}.mp.koread.cn'); //测试用,上线删除
  100. $modulename = $this->request->module();
  101. $controllername = strtolower($this->request->controller());
  102. $actionname = strtolower($this->request->action());
  103. $path = str_replace('.', '/', $controllername) . '/' . $actionname;
  104. // 定义是否Addtabs请求
  105. !defined('IS_ADDTABS') && define('IS_ADDTABS', input("addtabs") ? TRUE : FALSE);
  106. // 定义是否Dialog请求
  107. !defined('IS_DIALOG') && define('IS_DIALOG', input("dialog") ? TRUE : FALSE);
  108. // 定义是否AJAX请求
  109. !defined('IS_AJAX') && define('IS_AJAX', $this->request->isAjax());
  110. $this->auth = Auth::instance();
  111. // 设置当前请求的URI
  112. $this->auth->setRequestUri($path);
  113. // 检测是否需要验证登录
  114. if (!$this->auth->match($this->noNeedLogin))
  115. {
  116. //检测是否登录
  117. if (!$this->auth->isLogin())
  118. {
  119. Hook::listen('admin_nologin', $this);
  120. $url = Session::get('referer');
  121. $url = $url ? $url : get_url_no_port();
  122. // $this->error(__('Please login first'), url('index/login', ['url' => $url]));
  123. $this->redirect(url('index/login', ['url' => $url]));
  124. }
  125. // 判断账号是否已被禁止
  126. if ($this->auth->status != 'normal')
  127. {
  128. $url = Session::get('referer');
  129. $url = $url ? $url : get_url_no_port();
  130. $this->error('您的账号已无访问权限,请联系客服获取详情!', url('index/login', ['url' => $url]), '', 10);
  131. }
  132. // 判断是否需要验证权限
  133. if (!$this->auth->match($this->noNeedRight))
  134. {
  135. // 判断控制器和方法判断是否有对应权限
  136. if (!$this->auth->check($path))
  137. {
  138. Hook::listen('admin_nopermission', $this);
  139. $this->error(__('You have no permission'), '111');
  140. }
  141. }
  142. }
  143. if($this->auth->isLogin()){
  144. //密码修改后需要重新登录
  145. $admin = Session::get("admin");
  146. $groupIds = $this->auth->getGroupIds();
  147. $this->group = current($groupIds);
  148. $limit =AdminService::instance()->checkLimit($this->group, $admin['id'], Request::instance()->baseUrl(), Ip::ip());
  149. if ($limit->code != ErrorCodeConstants::SUCCESS) {
  150. $this->error($limit->msg, '', $limit->code);
  151. }
  152. //判断是否跳过强制授权
  153. $adminExtend = model('AdminExtend')->where('admin_id',$this->auth->id)->find();
  154. //判断配号代理商/渠道商,如果没有授权3个平台则跳转到授权页面,不弹出公告
  155. if(($this->group == 4 && $adminExtend->distribute == '1') || $this->group == 3){
  156. $adminCon = model('AdminConfig')->where('admin_id',$this->auth->id)->find();
  157. $is_fouce = $adminCon['is_fouce'];
  158. if($is_fouce == '1'){
  159. if(!model('Ptoken')->checkPlatformIsAuth($adminCon['platform_list'],$this->auth->id)){
  160. $this->platformAuthPage = true;
  161. if (!$this->request->isPost() && $controllername != "admin.config" && $controllername!='login' && $controllername!='loginout') { //检查是否需要跳转到授权页面
  162. Cookie::set('noNotice',1);
  163. $jumpUrl = Config::get('site.scheme').'://'.Config::get('site.url_root').'/admin/admin/config/index?ref=addtabs';
  164. $this->redirect($jumpUrl,[], 302);
  165. exit();
  166. }
  167. }else{
  168. if(Cookie::has('noNotice')){
  169. Cookie::delete('noNotice');
  170. }
  171. }
  172. }
  173. }
  174. $this->assign('backend_group',$this->group);
  175. //渠道商/代理商推广域名
  176. if ($this->group == 3) {
  177. //多账号切换
  178. $res = model('Relevance')->where("find_in_set({$this->auth->id},admin_ids)")->find();
  179. if(!empty($res)){
  180. $adminIds = $res->admin_ids;
  181. //多账号渠道添加到渠道城市白名单
  182. if($channelIds = explode(',',$adminIds)){
  183. foreach($channelIds as $id){
  184. WhiteList::addChannelCity($id);
  185. }
  186. }
  187. $admins = model('Admin')->where("id in({$adminIds})")->select();
  188. foreach($admins as $key=>$val){
  189. if($val->id == $this->auth->id){
  190. unset($admins[$key]);
  191. }
  192. }
  193. $this->assign('adminInfos',$admins);
  194. }
  195. }
  196. } else {
  197. $limit =AdminService::instance()->checkLimit(0, 0, Request::instance()->baseUrl(), Ip::ip());
  198. if ($limit->code != ErrorCodeConstants::SUCCESS) {
  199. $this->error($limit->msg, '', $limit->code);
  200. }
  201. }
  202. // 非选项卡时重定向
  203. if (!$this->request->isPost() && !IS_AJAX && !IS_ADDTABS && !IS_DIALOG && input("ref") == 'addtabs' && !$this->platformAuthPage)
  204. {
  205. $url = preg_replace_callback("/([\?|&]+)ref=addtabs(&?)/i", function ($matches) {
  206. return $matches[2] == '&' ? $matches[1] : '';
  207. }, get_url_no_port());
  208. $this->redirect('index/index', [], 302, ['referer' => $url]);
  209. exit;
  210. }
  211. //记录部分log信息
  212. Log::info('OS:' . Ua::getOs() . ' UA:' . Ua::getUa() . ' NetType:' . Ua::getNetType() . ' IP:' . Ip::ip() . ' [' . Ip::str() . '] admin_id:' . $this->auth->id . ' group:' . $this->group . ' REMOTE_ADDR:' . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '') . ' HTTP_X_FORWARDED_FOR:' . (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : ''));
  213. $post = json_encode($_POST, JSON_UNESCAPED_UNICODE);
  214. Log::info('POST:' . mb_substr($post, 0, 500));
  215. // 设置面包屑导航数据
  216. $breadcrumb = $this->auth->getBreadCrumb($path);
  217. array_pop($breadcrumb);
  218. $this->view->breadcrumb = $breadcrumb;
  219. // 如果有使用模板布局
  220. if ($this->layout)
  221. {
  222. $this->view->engine->layout('layout/' . $this->layout);
  223. }
  224. // 语言检测
  225. $lang = strip_tags(Lang::detect());
  226. $site = Config::get("site");
  227. // 配置后台默认主题
  228. $site['skin'] = $site['skin'] ?? 'skin-blue-light';
  229. $upload = \app\common\model\Config::upload();
  230. // 上传信息配置后
  231. Hook::listen("upload_config_init", $upload);
  232. // 配置信息
  233. $config = [
  234. 'site' => array_intersect_key($site, array_flip(['name', 'cdnurl', 'version', 'timezone', 'languages', 'scheme', 'url_agent', 'url_referral'])),
  235. 'upload' => $upload,
  236. 'modulename' => $modulename,
  237. 'controllername' => $controllername,
  238. 'actionname' => $actionname,
  239. 'jsname' => 'backend/' . str_replace('.', '/', $controllername),
  240. 'moduleurl' => rtrim(url("/{$modulename}", '', false), '/'),
  241. 'language' => $lang,
  242. 'fastadmin' => Config::get('fastadmin'),
  243. 'referer' => Session::get("referer")
  244. ];
  245. Config::set('upload', array_merge(Config::get('upload'), $upload));
  246. /**
  247. * 动态初始化微信开放平台参数
  248. */
  249. if($this->group == 3 || $this->group == 4) { //判断是否是渠道商 或者 代理商
  250. $channel_id = $this->auth->agent_id ? $this->auth->agent_id : $this->auth->channel_id; //判断配号代理商 或者 普通代理商/渠道商
  251. if ($adminInfo = model('AdminConfig')->getAdminInfoAll($channel_id)) {
  252. Config::set('wechat.app_id', $adminInfo['platform_appid']);
  253. Config::set('wechat.secret', $adminInfo['platform_secret']);
  254. Config::set('wechat.token', $adminInfo['platform_token']);
  255. Config::set('wechat.aes_key', $adminInfo['platform_aes_key']);
  256. }
  257. }
  258. // 配置信息后
  259. Hook::listen("config_init", $config);
  260. //加载当前控制器语言包
  261. $this->loadlang($controllername);
  262. //渲染站点配置
  263. $this->assign('site', $site);
  264. //渲染配置信息
  265. $this->assign('config', $config);
  266. //渲染权限对象
  267. $this->assign('auth', $this->auth);
  268. //渲染管理员对象
  269. $this->assign('admin', Session::get('admin'));
  270. /**
  271. * 下发当前用户的用户组
  272. */
  273. $this->assignconfig('group', $this->group);
  274. $this->assignconfig('admin_id', $this->auth->id);
  275. if (!$this->request->isAjax() && ($controllername != 'notice' || $actionname != 'show')) {
  276. //检查是否有需要弹出的公告dialog
  277. if(!Cookie::has('noNotice')){ //如果需要跳转到授权公众号页面则不弹出notice
  278. if (Session::has('notice_id')) {
  279. $this->assignconfig('notice_id', Session::get('notice_id'));
  280. }
  281. }
  282. }
  283. //KL的IP压入永久白名单
  284. WhiteList::addChannelIp($this->request->ip());
  285. //KL的渠道城市压入永久白名单
  286. if($this->group == 3 || $this->group == 4){
  287. WhiteList::addChannelCity($this->auth->id);
  288. }
  289. }
  290. /**
  291. * 加载语言文件
  292. * @param string $name
  293. */
  294. protected function loadlang($name)
  295. {
  296. Lang::load(APP_PATH . $this->request->module() . '/lang/' . Lang::detect() . '/' . str_replace('.', '/', $name) . '.php');
  297. }
  298. /**
  299. * 渲染配置信息
  300. * @param mixed $name 键名或数组
  301. * @param mixed $value 值
  302. */
  303. protected function assignconfig($name, $value = '')
  304. {
  305. $this->view->config = array_merge($this->view->config ? $this->view->config : [], is_array($name) ? $name : [$name => $value]);
  306. }
  307. /**
  308. * 生成查询所需要的条件,排序方式
  309. * @param mixed $searchfields 快速查询的字段
  310. * @param boolean $relationSearch 是否关联查询
  311. * @return array
  312. */
  313. protected function buildparams($searchfields = null, $relationSearch = null)
  314. {
  315. $searchfields = is_null($searchfields) ? $this->searchFields : $searchfields;
  316. $relationSearch = is_null($relationSearch) ? $this->relationSearch : $relationSearch;
  317. $search = $this->request->get("search", '');
  318. $filter = $this->request->get("filter", '');
  319. $op = $this->request->get("op", '', 'trim');
  320. $sort = $this->request->get("sort", "id");
  321. $order = $this->request->get("order", "DESC");
  322. $offset = $this->request->get("offset", 0);
  323. $limit = $this->request->get("limit", 0);
  324. $filter = json_decode($filter, TRUE);
  325. $op = json_decode($op, TRUE);
  326. $filter = $filter ? $filter : [];
  327. $where = [];
  328. $tableName = '';
  329. if ($relationSearch)
  330. {
  331. if (!empty($this->model))
  332. {
  333. $tableName = $this->model->getQuery()->getTable() . ".";
  334. }
  335. $sort = stripos($sort, ".") === false ? $tableName . $sort : $sort;
  336. }
  337. $adminIds = $this->getDataLimitAdminIds();
  338. if (is_array($adminIds))
  339. {
  340. $where[] = [$tableName . $this->dataLimitField, 'in', $adminIds];
  341. }
  342. if ($search)
  343. {
  344. $searcharr = is_array($searchfields) ? $searchfields : explode(',', $searchfields);
  345. foreach ($searcharr as $k => &$v)
  346. {
  347. $v = stripos($v, ".") === false ? $tableName . $v : $v;
  348. }
  349. unset($v);
  350. $where[] = [implode("|", $searcharr), "LIKE", "%{$search}%"];
  351. }
  352. foreach ($filter as $k => $v)
  353. {
  354. $sym = isset($op[$k]) ? $op[$k] : '=';
  355. if (stripos($k, ".") === false)
  356. {
  357. $k = $tableName . $k;
  358. }
  359. $sym = strtoupper(isset($op[$k]) ? $op[$k] : $sym);
  360. switch ($sym)
  361. {
  362. case '=':
  363. case '!=':
  364. $where[] = [$k, $sym, (string) $v];
  365. break;
  366. case 'LIKE':
  367. case 'NOT LIKE':
  368. case 'LIKE %...%':
  369. case 'NOT LIKE %...%':
  370. $where[] = [$k, trim(str_replace('%...%', '', $sym)), "%{$v}%"];
  371. break;
  372. case '>':
  373. case '>=':
  374. case '<':
  375. case '<=':
  376. $where[] = [$k, $sym, intval($v)];
  377. break;
  378. case 'IN':
  379. case 'IN(...)':
  380. case 'NOT IN':
  381. case 'NOT IN(...)':
  382. $where[] = [$k, str_replace('(...)', '', $sym), explode(',', $v)];
  383. break;
  384. case 'BETWEEN':
  385. case 'NOT BETWEEN':
  386. $arr = array_slice(explode(',', $v), 0, 2);
  387. if (stripos($v, ',') === false || !array_filter($arr))
  388. break;
  389. //当出现一边为空时改变操作符
  390. if ($arr[0] === '')
  391. {
  392. $sym = $sym == 'BETWEEN' ? '<=' : '>';
  393. $arr = $arr[1];
  394. }
  395. else if ($arr[1] === '')
  396. {
  397. $sym = $sym == 'BETWEEN' ? '>=' : '<';
  398. $arr = $arr[0];
  399. }
  400. $where[] = [$k, $sym, $arr];
  401. break;
  402. case 'RANGE':
  403. case 'NOT RANGE':
  404. $v = str_replace(' - ', ',', $v);
  405. $arr = array_slice(explode(',', $v), 0, 2);
  406. if (stripos($v, ',') === false || !array_filter($arr))
  407. break;
  408. //当出现一边为空时改变操作符
  409. if ($arr[0] === '')
  410. {
  411. $sym = $sym == 'RANGE' ? '<=' : '>';
  412. $arr = $arr[1];
  413. }
  414. else if ($arr[1] === '')
  415. {
  416. $sym = $sym == 'RANGE' ? '>=' : '<';
  417. $arr = $arr[0];
  418. }
  419. $where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' time', $arr];
  420. break;
  421. case 'LIKE':
  422. case 'LIKE %...%':
  423. $where[] = [$k, 'LIKE', "%{$v}%"];
  424. break;
  425. case 'NULL':
  426. case 'IS NULL':
  427. case 'NOT NULL':
  428. case 'IS NOT NULL':
  429. $where[] = [$k, strtolower(str_replace('IS ', '', $sym))];
  430. break;
  431. case 'FIND_IN_SET':
  432. $where[] = "FIND_IN_SET('{$v}', {$k})";
  433. break;
  434. default:
  435. break;
  436. }
  437. }
  438. $where = function($query) use ($where) {
  439. foreach ($where as $k => $v)
  440. {
  441. if (is_array($v))
  442. {
  443. call_user_func_array([$query, 'where'], $v);
  444. }
  445. else
  446. {
  447. $query->where($v);
  448. }
  449. }
  450. };
  451. return [$where, $sort, $order, $offset, $limit];
  452. }
  453. /**
  454. * 获取数据限制的管理员ID
  455. * 禁用数据限制时返回的是null
  456. * @return mixed
  457. */
  458. protected function getDataLimitAdminIds()
  459. {
  460. if (!$this->dataLimit)
  461. {
  462. return null;
  463. }
  464. if ($this->auth->isSuperAdmin())
  465. {
  466. return null;
  467. }
  468. $adminIds = [];
  469. if (in_array($this->dataLimit, ['auth', 'personal']))
  470. {
  471. $adminIds = $this->dataLimit == 'auth' ? $this->auth->getChildrenAdminIds(true) : [$this->auth->id];
  472. }
  473. return $adminIds;
  474. }
  475. /**
  476. * Selectpage的实现方法
  477. *
  478. * 当前方法只是一个比较通用的搜索匹配,请按需重载此方法来编写自己的搜索逻辑,$where按自己的需求写即可
  479. * 这里示例了所有的参数,所以比较复杂,实现上自己实现只需简单的几行即可
  480. *
  481. */
  482. protected function selectpage()
  483. {
  484. //设置过滤方法
  485. $this->request->filter(['strip_tags', 'htmlspecialchars']);
  486. //搜索关键词,客户端输入以空格分开,这里接收为数组
  487. $word = (array) $this->request->request("q_word/a");
  488. //当前页
  489. $page = $this->request->request("page");
  490. //分页大小
  491. $pagesize = $this->request->request("per_page");
  492. //搜索条件
  493. $andor = $this->request->request("and_or");
  494. //排序方式
  495. $orderby = (array) $this->request->request("order_by/a");
  496. //显示的字段
  497. $field = $this->request->request("field");
  498. //主键
  499. $primarykey = $this->request->request("pkey_name");
  500. //主键值
  501. $primaryvalue = $this->request->request("pkey_value");
  502. //搜索字段
  503. $searchfield = (array) $this->request->request("search_field/a");
  504. //自定义搜索条件
  505. $custom = (array) $this->request->request("custom/a");
  506. $order = [];
  507. foreach ($orderby as $k => $v)
  508. {
  509. $order[$v[0]] = $v[1];
  510. }
  511. $field = $field ? $field : 'name';
  512. //如果有primaryvalue,说明当前是初始化传值
  513. if ($primaryvalue !== null)
  514. {
  515. $where = [$primarykey => ['in', $primaryvalue]];
  516. }
  517. else
  518. {
  519. $where = function($query) use($word, $andor, $field, $searchfield, $custom) {
  520. foreach ($word as $k => $v)
  521. {
  522. foreach ($searchfield as $m => $n)
  523. {
  524. $query->where($n, "like", "%{$v}%", $andor);
  525. }
  526. }
  527. if ($custom && is_array($custom))
  528. {
  529. foreach ($custom as $k => $v)
  530. {
  531. $query->where($k, '=', $v);
  532. }
  533. }
  534. };
  535. }
  536. $adminIds = $this->getDataLimitAdminIds();
  537. if (is_array($adminIds))
  538. {
  539. $this->model->where($this->dataLimitField, 'in', $adminIds);
  540. }
  541. $list = [];
  542. $total = $this->model->where($where)->count();
  543. if ($total > 0)
  544. {
  545. if (is_array($adminIds))
  546. {
  547. $this->model->where($this->dataLimitField, 'in', $adminIds);
  548. }
  549. $list = $this->model->where($where)
  550. ->order($order)
  551. ->page($page, $pagesize)
  552. ->field("{$primarykey},{$field}")
  553. ->field("password,salt", true)
  554. ->select();
  555. }
  556. //这里一定要返回有list这个字段,total是可选的,如果total<=list的数量,则会隐藏分页按钮
  557. return json(['list' => $list, 'total' => $total]);
  558. }
  559. /**
  560. * 获取当前账号的渠道ID
  561. * @return mixed|null
  562. */
  563. protected function getCurrentAccountChannelId()
  564. {
  565. return $this->auth->agent_id ? $this->auth->agent_id : $this->auth->channel_id;
  566. }
  567. }