Api.php 11 KB

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