Api.php 13 KB

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