Api.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. <?php
  2. namespace app\controller;
  3. use app\BaseController;
  4. use app\model\FileModel;
  5. use app\model\LinkModel;
  6. use app\model\SettingModel;
  7. use GuzzleHttp\Client;
  8. use GuzzleHttp\Exception\RequestException;
  9. use PHPHtmlParser\Dom;
  10. use think\facade\Cache;
  11. use think\facade\Filesystem;
  12. use think\facade\Log;
  13. use think\facade\View;
  14. use think\helper\Str;
  15. class Api extends BaseController
  16. {
  17. public function site(): \think\response\Json
  18. {
  19. return $this->success("ok", [
  20. 'email' => $this->systemSetting('email', ''),
  21. 'qqGroup' => $this->systemSetting("qqGroup", ''),
  22. 'beianMps' => $this->systemSetting("beianMps", ''),
  23. 'copyright' => $this->systemSetting("copyright", ''),
  24. "recordNumber" => $this->systemSetting("recordNumber", ''),
  25. "mobileRecordNumber" => $this->systemSetting('mobileRecordNumber', '0'),
  26. "auth" => $this->auth,
  27. "logo" => $this->systemSetting('logo', ''),
  28. "qq_login" => $this->systemSetting('qq_login', '0'),
  29. "loginCloseRecordNumber" => $this->systemSetting('loginCloseRecordNumber', '0'),
  30. "is_push_link_store" => $this->auth ? $this->systemSetting('is_push_link_store', '0') : '0',
  31. "is_push_link_store_tips" => $this->systemSetting('is_push_link_store_tips', '0'),
  32. "is_push_link_status" => $this->systemSetting("is_push_link_status", '0'),
  33. 'google_ext_link' => $this->systemSetting("google_ext_link", ''),
  34. 'edge_ext_link' => $this->systemSetting("edge_ext_link", ''),
  35. 'local_ext_link' => $this->systemSetting("local_ext_link", ''),
  36. "customAbout" => $this->systemSetting("customAbout", ''),
  37. "user_register" => $this->systemSetting("user_register", '0', true),
  38. "tip"=>[
  39. "ds_status"=> $this->systemSetting('ds_status', '0', true),
  40. "ds_template"=> $this->systemSetting('ds_template', 'org', true),
  41. "ds_alipay_img"=>$this->systemSetting('ds_alipay_img', '', true),
  42. "ds_wx_img"=>$this->systemSetting('ds_wx_img', '', true),
  43. "ds_custom_url"=>$this->systemSetting("ds_custom_url",'',true),
  44. 'ds_title' => $this->systemSetting('ds_title', '', true),
  45. 'ds_tips' => $this->systemSetting('ds_tips', '', true)
  46. ]
  47. ]);
  48. }
  49. public function background(): \think\response\File
  50. {
  51. return download('static/background.jpeg', 'background.jpeg')->mimeType(\PluginStaticSystem::mimeType('static/background.jpeg'))->force(false)->expire(60 * 60 * 24 * 3);
  52. }
  53. //获取默认壁纸
  54. function DefBg(): \think\response\Json
  55. {
  56. $config = $this->systemSetting('defaultTab', 'static/defaultTab.json', true);
  57. if ($config) {
  58. $fp = public_path() . $config;
  59. if (file_exists($fp)) {
  60. $file = file_get_contents($fp);
  61. $json = json_decode($file, true);
  62. if (isset($json['config']['theme']['backgroundImage'])) {
  63. $bg = $json['config']['theme']['backgroundImage'];
  64. $bgMime = $json['config']['theme']['backgroundMime'] ?? 0;
  65. return $this->success("ok", ['background' => $bg, "mime" => $bgMime]);
  66. }
  67. }
  68. }
  69. return $this->success("ok", ['background' => "static/background.jpeg", "mime" => 0]);
  70. }
  71. function globalNotify(): \think\response\Json
  72. {
  73. $info = SettingModel::Config("globalNotify", false);
  74. if ($info) {
  75. return $this->success('ok', $info);
  76. }
  77. return $this->error('empty');
  78. }
  79. //获取邮件验证码
  80. function getMailCode(): \think\response\Json
  81. {
  82. $mail = $this->request->post("mail", false);
  83. $code = rand(100000, 999999);
  84. if ($mail) {
  85. if (Cache::get('code' . $mail)) {
  86. return $this->success("请勿频繁获取验证码");
  87. }
  88. $k = SettingModel::Config('smtp_code_template', false);
  89. if ($k === false || mb_strlen(trim($k)) == 0) {
  90. $k = '
  91. <div style="border:1px #DEDEDE solid;border-top:3px #009944 solid;padding:25px;background-color:#FFF;">
  92. <div style="font-size:17px;font-weight:bold;">邮箱验证码</div>
  93. <div style="font-size:14px;line-height:36px;padding-top:15px;padding-bottom:15px;">
  94. 尊敬的用户,您好!<br>
  95. 您的验证码是:<b style="color: #1e9fff">{$code}</b>。5分钟内有效,请尽快验证。
  96. </div>
  97. <div style="line-height:15px;">
  98. 此致
  99. </div>
  100. </div>
  101. ';
  102. }
  103. $html = View::display($k, ['time' => date('Y-m-d H:i:s'), 'code' => $code]);
  104. try {
  105. $status = \Mail::send($mail, $html);
  106. if ($status) {
  107. Cache::set('code' . $mail, $code, 300);
  108. return $this->success('发送成功');
  109. }
  110. } catch (\Exception $e) {
  111. return $this->error($e->getMessage());
  112. }
  113. }
  114. return $this->error('发送失败');
  115. }
  116. private function addHttpProtocolRemovePath($url): string
  117. {
  118. // 解析URL
  119. $parsedUrl = parse_url($url);
  120. // 检查是否已经有协议,如果没有则添加http://
  121. if (!isset($parsedUrl['scheme'])) {
  122. // 检查是否以 // 开头,如果是,则转换为相对协议
  123. if (isset($parsedUrl['host']) && strpos($url, '//') === 0) {
  124. $url = 'http:' . $url;
  125. } else {
  126. $url = 'http://' . $url;
  127. }
  128. } else {
  129. // 如果有协议但没有路径,保留原样
  130. $url = $parsedUrl['scheme'] . '://';
  131. // 如果有主机,则添加主机部分
  132. if (isset($parsedUrl['host'])) {
  133. $url .= $parsedUrl['host'];
  134. // 如果有端口号,则添加端口号
  135. if (isset($parsedUrl['port'])) {
  136. $url .= ':' . $parsedUrl['port'];
  137. }
  138. }
  139. }
  140. return $url;
  141. }
  142. private function addHttpProtocol($url)
  143. {
  144. // 检查是否已经有协议,如果没有则添加http://
  145. if (!parse_url($url, PHP_URL_SCHEME)) {
  146. // 检查是否以 // 开头,如果是,则转换为相对协议
  147. if (strpos($url, '//') === 0) {
  148. $url = 'https:' . $url;
  149. } else {
  150. $url = 'http://' . $url;
  151. }
  152. }
  153. return $url;
  154. }
  155. private function hasOnlyPath($url): bool
  156. {
  157. if(!$url){
  158. return false;
  159. }
  160. $parsedUrl = parse_url($url);
  161. // 检查是否存在路径但不存在域名和协议
  162. if (isset($parsedUrl['path']) && !isset($parsedUrl['host']) && !isset($parsedUrl['scheme'])) {
  163. return true;
  164. }
  165. return false;
  166. }
  167. function getIcon(): \think\response\Json
  168. {
  169. $avatar = $this->request->post('avatar');
  170. if ($avatar) {
  171. $remote_avatar = $this->systemSetting("remote_avatar", "https://avatar.mtab.cc/6.x/icons/png?seed=", true);
  172. $str = $this->downloadFile($remote_avatar . $avatar, md5($avatar) . '.png');
  173. return $this->success(['src' => $str]);
  174. }
  175. $url = $this->request->post('url', false);
  176. $icon = "";
  177. $cdn = $this->systemSetting('assets_host', '');
  178. $title = "";
  179. if ($url) {
  180. $realUrl = $this->addHttpProtocolRemovePath($url);
  181. $client = \Axios::http();
  182. try {
  183. $response = $client->get($realUrl, [
  184. 'headers' => [
  185. "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
  186. ]
  187. ]);
  188. $status = $response->getStatusCode();
  189. } catch (\Exception $e) {
  190. return $this->error('无法连接远程目标服务器');
  191. }
  192. if ($status == 200) {
  193. $body = $response->getBody()->getContents();
  194. $dom = new Dom();
  195. $dom->loadStr($body);
  196. $titles = $dom->find('title');
  197. if (count($titles) > 0) {
  198. $title = $titles->innerText;
  199. }
  200. try {
  201. $list = $dom->find('[rel="icon"]');
  202. if (count($list) > 0) {
  203. $href = $list->href;
  204. if ($this->hasOnlyPath($href)) {
  205. if ($href[0] != '/') {
  206. $href = "/" . $href;
  207. }
  208. $href = $realUrl . $href;
  209. }
  210. $href = $this->addHttpProtocol($href);
  211. $icon = $href;
  212. $response = \Axios::http()->get($icon, [
  213. 'headers' => [
  214. 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
  215. ]
  216. ]);
  217. $contentType = $response->getHeader('content-type');
  218. $contentType = $contentType[0];
  219. if (preg_match('/(png|jpg|jpeg|x-icon|svg\+xml)$/', $contentType, $matches)) {
  220. $contentType = array(
  221. 'png' => 'png',
  222. 'jpg' => 'jpg',
  223. 'jpeg' => 'jpeg',
  224. 'x-icon' => 'ico',
  225. 'svg+xml' => 'svg',
  226. );
  227. $fileFormat = $matches[1];
  228. $icon = $this->downloadFile($icon, md5($realUrl) . '.' . $contentType[$fileFormat]);
  229. if ($icon) {
  230. $icon = $cdn . $icon;
  231. }
  232. } else {
  233. $icon = '';
  234. }
  235. }
  236. } catch (\ErrorException $e) {
  237. }
  238. }
  239. if (strlen($icon) == 0) {
  240. try {
  241. $client = \Axios::http();
  242. $response = $client->get($realUrl . '/favicon.ico');
  243. $status = $response->getStatusCode();
  244. if ($status == 200) {
  245. $icon = $realUrl . '/favicon.ico';
  246. $icon = $this->downloadFile($icon, md5($realUrl) . '.ico');
  247. if ($icon) {
  248. $icon = $cdn . $icon;
  249. }
  250. }
  251. } catch (\Exception $e) {
  252. }
  253. }
  254. if (strlen($icon) > 0) {
  255. return $this->success(['src' => $icon, 'name' => $title]);
  256. }
  257. }
  258. return $this->error('没有抓取到图标');
  259. }
  260. private function downloadFile($url, $name)
  261. {
  262. $user = $this->getUser();
  263. $client = \Axios::http();
  264. $path = '/images/' . date('Y/m/d/');
  265. $remotePath = public_path() . $path;
  266. $downloadPath = $remotePath . $name;
  267. if (!is_dir($remotePath)) {
  268. mkdir($remotePath, 0755, true);
  269. }
  270. try {
  271. $response = $client->request('GET', $url, [
  272. 'sink' => $downloadPath,
  273. 'headers' => [
  274. 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
  275. ]
  276. ]);
  277. FileModel::addFile($path . $name, $user['user_id'] ?? null);
  278. return $path . $name;
  279. } catch (RequestException $e) {
  280. }
  281. return false;
  282. }
  283. function renderIco(): \think\Response
  284. {
  285. $send = $this->request->get('seed');
  286. $client = new Client();
  287. $remote_avatar = $this->systemSetting('remote_avatar', 'https://avatar.mtab.cc/6.x/icons/png?seed=', true);
  288. $response = $client->get($remote_avatar . urlencode($send), [
  289. 'stream' => true,
  290. 'timeout' => 10,
  291. ]);
  292. return response($response->getBody(), 200, ['Content-Type' => $response->getHeader("content-type")[0]]);
  293. }
  294. function upload(): \think\response\Json
  295. {
  296. $user = $this->getUser();
  297. if (!$user) {
  298. if ($this->systemSetting('touristUpload') !== '1') {
  299. //如果没有开启游客上传
  300. return $this->error('管理员已关闭游客上传!请登录后使用');
  301. }
  302. }
  303. $type = $this->request->header("Up-Type", '');
  304. $file = $this->request->file('file');
  305. if (empty($file)) {
  306. return $this->error('not File');
  307. }
  308. $maxSize = (double)$this->systemSetting('upload_size', '2');
  309. if ($file->getSize() > 1024 * 1024 * $maxSize) {
  310. $limit = $maxSize < 1 ? ($maxSize * 1000) . 'KB' : ($maxSize) . 'MB';
  311. return $this->error("文件最大$limit,请压缩后再试");
  312. }
  313. if (in_array(strtolower($file->getOriginalExtension()), ['png', 'jpg', 'jpeg', 'webp', 'ico', 'svg'])) {
  314. // 验证文件并保存
  315. try {
  316. // 构建保存路径
  317. $savePath = '/images/' . date('Y/m/d');
  318. $hash = Str::random(32);
  319. $fileName = $hash . '.' . $file->getOriginalExtension();
  320. $filePath = Filesystem::disk('images')->putFileAs($savePath, $file, $fileName);
  321. $minPath = '';
  322. if ($type == 'icon' || $type == 'avatar') {
  323. $fp = joinPath(public_path(), $filePath);
  324. $image = new \ImageBack($fp);
  325. $image->resize(144, 0)->save($fp);
  326. } else if ($type == 'AdminBackground') {
  327. $minPath = joinPath($savePath, "/min_$fileName");
  328. $fp = joinPath(public_path(), $filePath);
  329. $image = new \ImageBack($fp);
  330. $image->resize(400, 0)->save(joinPath(public_path(), $minPath));
  331. FileModel::addFile($minPath, $user['user_id'] ?? null);
  332. }
  333. $cdn = $this->systemSetting('assets_host', '/', true);
  334. FileModel::addFile($filePath, $user['user_id'] ?? null);
  335. return $this->success(['url' => $cdn . $filePath, "minUrl" => joinPath($cdn, $minPath)]);
  336. } catch (\think\exception\ValidateException $e) {
  337. return $this->error($e->getMessage());
  338. // 验证失败,给出错误提示
  339. // ...
  340. }
  341. }
  342. return $this->error('上传失败');
  343. }
  344. function AdminUpload(): \think\response\Json
  345. {
  346. $user = $this->getAdmin();
  347. $file = $this->request->file('file');
  348. if (empty($file)) {
  349. return $this->error('not File');
  350. }
  351. if ($file->getSize() > 1024 * 1024 * 5) {
  352. return $this->error('max fileSize is 5M');
  353. }
  354. // 验证文件并保存
  355. try {
  356. // 构建保存路径
  357. $savePath = '/images/' . date('Y/m/d');
  358. $hash = Str::random(32);
  359. $fileName = $hash . '.' . $file->getOriginalExtension();
  360. $filePath = Filesystem::disk('images')->putFileAs($savePath, $file, $fileName);
  361. $cdn = $this->systemSetting('assets_host', '/', true);
  362. FileModel::addFile($filePath, $user['user_id'] ?? null);
  363. return $this->success(['url' => $cdn . $filePath]);
  364. } catch (\think\exception\ValidateException $e) {
  365. // 验证失败,给出错误提示
  366. // ...
  367. }
  368. return $this->error('上传失败');
  369. }
  370. function refresh(): \think\response\Json
  371. {
  372. $user = $this->getUser();
  373. if ($user) {
  374. $data = [];
  375. $data['link_update_time'] = LinkModel::where("user_id", $user['user_id'])->value("update_time");
  376. return $this->success("ok", $data);
  377. }
  378. return $this->error("not login");
  379. }
  380. }