* blog:xiaoz.me */ class Api { protected $db; public function __construct($db){ $this->db = $db; //返回json类型 header('Content-Type:application/json; charset=utf-8'); } /** * name:创建分类目录 */ public function add_category($token,$name,$property = 0,$weight = 0,$description = '',$font_icon = '',$fid = 0){ $this->auth($token); //分类名称不允许为空 if( empty($name) ) { $this->err_msg(-2000,'分类名称不能为空!'); } $data = [ 'name' => htmlspecialchars($name,ENT_QUOTES), 'add_time' => time(), 'weight' => $weight, 'property' => $property, 'description' => htmlspecialchars($description,ENT_QUOTES), 'font_icon' => $font_icon, 'fid' => $fid ]; //插入分类目录 $this->db->insert("on_categorys",$data); //返回ID $id = $this->db->id(); //如果id为空(NULL),说明插入失败了,姑且认为是name重复导致 if( empty($id) ){ $this->err_msg(-1000,'Categorie already exist!'); } else{ //成功并返回json格式 $data = [ 'code' => 0, 'id' => intval($id) ]; exit(json_encode($data)); } } /** * 修改分类目录 * */ public function edit_category($token,$id,$name,$property = 0,$weight = 0,$description = '',$font_icon = '',$fid = 0){ $this->auth($token); //如果id为空 if( empty($id) ){ $this->err_msg(-1003,'The category ID cannot be empty!'); } //如果分类名为空 elseif( empty($name ) ){ $this->err_msg(-1004,'The category name cannot be empty!'); } //更新数据库 else{ //根据分类ID查询改分类下面是否已经存在子分类,如果存在子分类了则不允许设置为子分类,实用情况:一级分类下存在二级分类,无法再将改一级分类修改为二级分类 $count = $this->db->count("on_categorys", [ "fid" => $id ]); if( $count > 0 ) { $this->err_msg(-2000,'修改失败,该分类下已存在子分类!'); } $data = [ 'name' => htmlspecialchars($name,ENT_QUOTES), 'up_time' => time(), 'weight' => $weight, 'property' => $property, 'description' => htmlspecialchars($description,ENT_QUOTES), 'font_icon' => $font_icon, 'fid' => $fid ]; $re = $this->db->update('on_categorys',$data,[ 'id' => $id]); //var_dump( $this->db->log() ); //获取影响行数 $row = $re->rowCount(); if($row) { $data = [ 'code' => 0, 'msg' => 'successful' ]; exit(json_encode($data)); } else{ $this->err_msg(-1005,'The category name already exists!'); } } } /** * 删除分类目录 */ public function del_category($token,$id) { //验证授权 $this->auth($token); //如果id为空 if( empty($id) ){ $this->err_msg(-1003,'The category ID cannot be empty!'); } //如果分类目录下存在数据 $count = $this->db->count("on_links", [ "fid" => $id ]); //如果分类目录下存在数据,则不允许删除 if($count > 0) { $this->err_msg(-1006,'The category is not empty and cannot be deleted!'); } else{ $data = $this->db->delete('on_categorys',[ 'id' => $id] ); //返回影响行数 $row = $data->rowCount(); if($row) { $data = [ 'code' => 0, 'msg' => 'successful' ]; exit(json_encode($data)); } else{ $this->err_msg(-1007,'The category delete failed!'); } } } /** * name:返回错误(json) * */ protected function err_msg($code,$err_msg){ $data = [ 'code' => $code, 'err_msg' => $err_msg ]; //返回json类型 header('Content-Type:application/json; charset=utf-8'); exit(json_encode($data)); } /** * name:验证方法 */ protected function auth($token){ //计算正确的token:用户名 + TOKEN $SecretKey = @$this->db->get('on_options','*',[ 'key' => 'SecretKey' ])['value']; $token_yes = md5(USER.$SecretKey); //如果token为空,则验证cookie if(empty($token)) { if( !$this->is_login() ) { $this->err_msg(-1002,'Authorization failure!'); } } else if ( empty($SecretKey) ) { $this->err_msg(-2000,'请先生成SecretKey!'); } else if($token != $token_yes){ $this->err_msg(-1002,'Authorization failure!'); } else{ return true; } } /** * name:添加链接 */ public function add_link($token,$fid,$title,$url,$description = '',$weight = 0,$property = 0,$url_standby = ''){ $this->auth($token); $fid = intval($fid); //检测链接是否合法 //$this->check_link($fid,$title,$url); $this->check_link([ 'fid' => $fid, 'title' => $title, 'url' => $url, 'url_standby' => $url_standby ]); //合并数据 $data = [ 'fid' => $fid, 'title' => htmlspecialchars($title,ENT_QUOTES), 'url' => htmlspecialchars($url,ENT_QUOTES), 'url_standby' => htmlspecialchars($url_standby,ENT_QUOTES), 'description' => htmlspecialchars($description,ENT_QUOTES), 'add_time' => time(), 'weight' => $weight, 'property' => $property ]; //插入数据库 $re = $this->db->insert('on_links',$data); //返回影响行数 $row = $re->rowCount(); //如果为真 if( $row ){ $id = $this->db->id(); $data = [ 'code' => 0, 'id' => $id ]; exit(json_encode($data)); } //如果插入失败 else{ $this->err_msg(-1011,'The URL already exists!'); } } /** * 批量导入链接 */ public function imp_link($token,$filename,$fid,$property = 0){ //过滤$filename $filename = str_replace('../','',$filename); $filename = str_replace('./','',$filename); $this->auth($token); //检查文件是否存在 if ( !file_exists($filename) ) { $this->err_msg(-1016,'File does not exist!'); } //解析HTML数据 $content = file_get_contents($filename); $pattern = "//i"; preg_match_all($pattern,$content,$arr); //失败次数 $fail = 0; //成功次数 $success = 0; //总数 $total = count($arr[0]); foreach( $arr[0] as $link ) { $pattern = "/http.*\"? ADD_DATE/i"; preg_match($pattern,$link,$urls); $url = str_replace('" ADD_DATE','',$urls[0]); $pattern = "/>.*<\/a>$/i"; preg_match($pattern,$link,$titles); $title = str_replace('>','',$titles[0]); $title = str_replace(' $fid, 'description' => '', 'add_time' => time(), 'weight' => 0, 'property' => $property ]; $data['title'] = $title; $data['url'] = $url; //插入数据库 $re = $this->db->insert('on_links',$data); //返回影响行数 $row = $re->rowCount(); //如果为真 if( $row ){ $id = $this->db->id(); $data = [ 'code' => 0, 'id' => $id ]; $success++; } //如果插入失败 else{ $fail++; } } //删除书签 unlink($filename); $data = [ 'code' => 0, 'msg' => '总数:'.$total.' 成功:'.$success.' 失败:'.$fail ]; exit(json_encode($data)); } /** * 书签上传 * type:上传类型,默认为上传书签,后续类型保留使用 */ public function upload($token,$type){ $this->auth($token); if ($_FILES["file"]["error"] > 0) { $this->err_msg(-1015,'File upload failed!'); } else { $filename = $_FILES["file"]["name"]; //获取文件后缀 $suffix = explode('.',$filename); $suffix = strtolower(end($suffix)); //临时文件位置 $temp = $_FILES["file"]["tmp_name"]; if( $suffix != 'html' ) { //删除临时文件 unlink($filename); $this->err_msg(-1014,'Unsupported file suffix name!'); } if( copy($temp,'data/'.$filename) ) { $data = [ 'code' => 0, 'file_name' => 'data/'.$filename ]; exit(json_encode($data)); } } } /** * name:修改链接 */ public function edit_link($token,$id,$fid,$title,$url,$description = '',$weight = 0,$property = 0,$url_standby = ''){ $this->auth($token); $fid = intval($fid); //检测链接是否合法 //$this->check_link($fid,$title,$url); $this->check_link([ 'fid' => $fid, 'title' => htmlspecialchars($title,ENT_QUOTES), 'url' => htmlspecialchars($url,ENT_QUOTES), 'url_standby' => htmlspecialchars($url_standby,ENT_QUOTES) ]); //查询ID是否存在 $count = $this->db->count('on_links',[ 'id' => $id]); //如果id不存在 if( (empty($id)) || ($count == false) ) { $this->err_msg(-1012,'link id not exists!'); } //合并数据 $data = [ 'fid' => $fid, 'title' => htmlspecialchars($title,ENT_QUOTES), 'url' => $url, 'url_standby' => $url_standby, 'description' => htmlspecialchars($description,ENT_QUOTES), 'up_time' => time(), 'weight' => $weight, 'property' => $property ]; //插入数据库 $re = $this->db->update('on_links',$data,[ 'id' => $id]); //返回影响行数 $row = $re->rowCount(); //如果为真 if( $row ){ $id = $this->db->id(); $data = [ 'code' => 0, 'msg' => 'successful' ]; exit(json_encode($data)); } //如果插入失败 else{ $this->err_msg(-1011,'The URL already exists!'); } } /** * 删除链接 */ public function del_link($token,$id){ //验证token是否合法 $this->auth($token); //查询ID是否存在 $count = $this->db->count('on_links',[ 'id' => $id]); //如果id不存在 if( (empty($id)) || ($count == false) ) { $this->err_msg(-1010,'link id not exists!'); } else{ $re = $this->db->delete('on_links',[ 'id' => $id] ); if($re) { $data = [ 'code' => 0, 'msg' => 'successful' ]; exit(json_encode($data)); } else{ $this->err_msg(-1010,'link id not exists!'); } } } /** * 验证链接合法性 * 接收一个数组作为参数 */ protected function check_link($data){ $fid = $data['fid']; $title = $data['title']; $url = $data['url']; $url_standby = @$data['url_standby']; //如果父及(分类)ID不存在 if( empty($fid )) { $this->err_msg(-1007,'The category id(fid) not exist!'); } //如果父及ID不存在数据库中 //验证分类目录是否存在 $count = $this->db->count("on_categorys", [ "id" => $fid ]); if ( empty($count) ){ $this->err_msg(-1007,'The category not exist!'); } //如果链接标题为空 if( empty($title) ){ $this->err_msg(-1008,'The title cannot be empty!'); } //链接不能为空 if( empty($url) ){ $this->err_msg(-1009,'URL cannot be empty!'); } //通过正则匹配链接是否合法,支持http/https/ftp/magnet:?|ed2k|tcp/udp/thunder/rtsp/rtmp/sftp $pattern = "/^(http:\/\/|https:\/\/|ftp:\/\/|ftps:\/\/|magnet:?|ed2k:\/\/|tcp:\/\/|udp:\/\/|thunder:\/\/|rtsp:\/\/|rtmp:\/\/|sftp:\/\/).+/"; // if( !filter_var($url, FILTER_VALIDATE_URL) ) { // $this->err_msg(-1010,'URL is not valid!'); // } if ( !preg_match($pattern,$url) ) { $this->err_msg(-1010,'URL is not valid!'); } //备用链接不合法 if ( ( !empty($url_standby) ) && ( !preg_match($pattern, $url_standby) ) ) { $this->err_msg(-1010,'URL is not valid!'); } return true; } /** * 查询分类目录 */ public function category_list($page,$limit){ $token = @$_POST['token']; $offset = ($page - 1) * $limit; //如果成功登录,则查询所有 if( $this->is_login() ){ $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = a.fid LIMIT 1) AS fname FROM on_categorys as a ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}"; //统计总数 $count = $this->db->count('on_categorys','*'); } //如果存在token,则验证 else if( !empty($token) ) { $this->auth($token); //查询所有分类 $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = a.fid LIMIT 1) AS fname FROM on_categorys as a ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}"; //统计总数 $count = $this->db->count('on_categorys','*'); } else{ $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = a.fid LIMIT 1) AS fname FROM on_categorys as a WHERE property = 0 ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}"; //统计总数 $count = $this->db->count('on_categorys','*',[ "property" => 0 ]); } //原生查询 $datas = $this->db->query($sql)->fetchAll(); $datas = [ 'code' => 0, 'msg' => '', 'count' => $count, 'data' => $datas ]; exit(json_encode($datas)); } /** * 生成 */ public function create_sk() { //验证是否登录 $this->auth(''); $sk = md5(USER.USER.time()); $result = $this->set_option_bool('SecretKey',$sk); if( $result ){ $datas = [ 'code' => 0, 'data' => $sk ]; exit(json_encode($datas)); } else{ $this->err_msg(-2000,'SecretKey生成失败!'); } } /** * 查询链接 * 接收一个数组作为参数 */ public function link_list($data){ $limit = $data['limit']; $token = $data['token']; $offset = ($data['page'] - 1) * $data['limit']; $fid = @$data['category_id']; //如果存在分类ID,则根据分类ID进行查询 if ($data['category_id'] != null) { $cid_sql = "WHERE fid = $fid"; //统计链接总数 $count = $this->db->count('on_links','*',[ 'fid' => $fid ]); } else{ //统计链接总数,没有分类ID的情况 $count = $this->db->count('on_links','*'); } //如果成功登录,但token为空 if( ($this->is_login()) && (empty($token)) ){ $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links ${cid_sql} ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}"; } //如果token验证通过 elseif( (!empty($token)) && ($this->auth($token)) ) { $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links ${cid_sql} ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}"; } //如果即没有登录成功,又没有token,则默认为游客 else{ $cid_sql = empty($fid) ? null : "AND fid = $fid"; if($cid_sql == null) { //统计链接总数,不存在分类ID的情况 $count = $this->db->count('on_links','*',[ 'property' => 0 ]); } else{ //统计链接总数,存在分类ID的情况 $count = $this->db->count('on_links','*',[ 'property' => 0, 'fid' => $fid ]); } $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links WHERE property = 0 ${cid_sql} ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}"; } //打印SQL //echo $sql; //如果查询的总数大于limit,则以limit为准 //$count = ( $count > $limit) ? $limit : $count; //原生查询 $datas = $this->db->query($sql)->fetchAll(); $datas = [ 'code' => 0, 'msg' => '', 'count' => $count, 'data' => $datas ]; exit(json_encode($datas)); } /** * 查询单个链接 * 此函数接收一个数组 */ public function get_a_link($data) { $id = $data['id']; $token = $data['token']; $link_info = $this->db->get("on_links","*",[ "id" => $id ]); //打印链接信息 //var_dump($link_info); //如果是公开链接,则直接返回 if ( $link_info['property'] == "0" ) { $datas = [ 'code' => 0, 'data' => $link_info ]; } //如果是私有链接,并且认证通过 elseif( $link_info['property'] == "1" ) { if ( ( $this->auth($token) ) || ( $this->is_login() ) ) { $datas = [ 'code' => 0, 'data' => $link_info ]; } //exit(json_encode($datas)); } //如果是其它情况,则显示为空 else{ $datas = [ 'code' => 0, 'data' => [] ]; //exit(json_encode($datas)); } exit(json_encode($datas)); } /** * 查询单个分类信息 * 此函数接收一个数组 */ public function get_a_category($data) { $id = $data['id']; $token = $data['token']; $category_info = $this->db->get("on_categorys","*",[ "id" => $id ]); //var_dump($category_info); //如果是公开分类,则直接返回 if ( $category_info['property'] == "0" ) { $datas = [ 'code' => 0, 'data' => $category_info ]; } //如果是私有链接,并且认证通过 elseif( $category_info['property'] == "1" ) { if ( ( $this->auth($token) ) || ( $this->is_login() ) ) { $datas = [ 'code' => 0, 'data' => $category_info ]; } //exit(json_encode($datas)); } //如果是其它情况,则显示为空 else{ $datas = [ 'code' => 0, 'data' => [] ]; //exit(json_encode($datas)); } exit(json_encode($datas)); } /** * 验证是否登录 */ protected function is_login(){ $key = md5(USER.PASSWORD.'onenav'); //获取session $session = $_COOKIE['key']; //如果已经成功登录 if($session == $key) { return true; } else{ return false; } } /** * 获取链接信息 */ public function get_link_info($token,$url){ $this->auth($token); //检查链接是否合法 $pattern = "/^(http:\/\/|https:\/\/).*/"; //链接不合法 if( empty($url) ) { $this->err_msg(-2000,'URL不能为空!'); } if( !preg_match($pattern,$url) ){ $this->err_msg(-1010,'只支持识别http/https协议的链接!'); } //获取网站标题 $c = curl_init(); curl_setopt($c, CURLOPT_URL, $url); curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($c, CURLOPT_SSL_VERIFYHOST, false); //设置超时时间 curl_setopt($c , CURLOPT_TIMEOUT, 10); $data = curl_exec($c); curl_close($c); $pos = strpos($data,'utf-8'); if($pos===false){$data = iconv("gbk","utf-8",$data);} preg_match("/(.*)<\/title>/i",$data, $title); $link['title'] = $title[1]; //获取网站描述 $tags = get_meta_tags($url); $link['description'] = $tags['description']; $data = [ 'code' => 0, 'data' => $link ]; exit(json_encode($data)); } /** * 自定义js */ public function add_js($token,$content){ $this->auth($token); //如果内容为空 // if( $content == '' ){ // $this->err_msg(-1013,'The content cannot be empty!'); // } //写入文件 try{ file_put_contents("data/extend.js",$content); $data = [ 'code' => 0, 'data' => 'success' ]; exit(json_encode($data)); } catch(Exception $e){ $this->err_msg(-2000,$e->getMessage()); } } /** * 获取IP */ //获取访客IP protected function getIP() { if (getenv('HTTP_CLIENT_IP')) { $ip = getenv('HTTP_CLIENT_IP'); } elseif (getenv('HTTP_X_FORWARDED_FOR')) { $ip = getenv('HTTP_X_FORWARDED_FOR'); } elseif (getenv('HTTP_X_FORWARDED')) { $ip = getenv('HTTP_X_FORWARDED'); } elseif (getenv('HTTP_FORWARDED_FOR')) { $ip = getenv('HTTP_FORWARDED_FOR'); } elseif (getenv('HTTP_FORWARDED')) { $ip = getenv('HTTP_FORWARDED'); } else { $ip = $_SERVER['REMOTE_ADDR']; } return $ip; } /** * name:检查弱密码 */ public function check_weak_password($token){ $this->auth($token); //如果用户名、密码为初始密码,则提示修改 if ( ( USER == 'xiaoz' ) && ( PASSWORD == 'xiaoz.me' ) ) { $this->err_msg(-1,'Weak password!'); } } /** * 获取SQL更新列表 * 循环读取db/sql/目录下的.sql文件 */ public function get_sql_update_list($data) { //鉴权 if( !$this->is_login() ) { $this->err_msg(-1002,'Authorization failure!'); } //待更新的数据库文件目录 $sql_dir = 'db/sql/'; //待更新的sql文件列表,默认为空 $sql_files_all = []; //打开一个目录,读取里面的文件列表 if (is_dir($sql_dir)){ if ($dh = opendir($sql_dir)){ while (($file = readdir($dh)) !== false){ //排除.和.. if ( ($file != ".") && ($file != "..") ) { array_push($sql_files_all,$file); } } //关闭句柄 closedir($dh); } } //判断数据库日志表是否存在 $sql = "SELECT count(*) AS num FROM sqlite_master WHERE type='table' AND name='on_db_logs'"; //查询结果 $q_result = $this->db->query($sql)->fetchAll(); //如果数量为0,则说明on_db_logs这个表不存在,需要提前导入 $num = intval($q_result[0]['num']); if ( $num === 0 ) { $data = [ "code" => 0, "data" => ['on_db_logs.sql'] ]; exit(json_encode($data)); }else{ //如果不为0,则需要查询数据库更新表里面的数据进行差集比对 $get_on_db_logs = $this->db->select("on_db_logs",[ "sql_name" ],[ "status" => "TRUE" ]); //声明一个空数组,存储已更新的数据库列表 $already_dbs = []; foreach ($get_on_db_logs as $value) { array_push($already_dbs,$value['sql_name']); } //array_diff() 函数返回两个数组的差集数组 $diff_result = array_diff($sql_files_all,$already_dbs); //去掉键 $diff_result = array_values($diff_result); sort($diff_result); $data = [ "code" => 0, "data" => $diff_result ]; exit(json_encode($data)); } } /** * 执行SQL更新语句,只执行单条更新 */ public function exe_sql($data) { //鉴权 if( !$this->is_login() ) { $this->err_msg(-1002,'Authorization failure!'); } //数据库sql目录 $sql_dir = 'db/sql/'; $name = $data['name']; //查询sql是否已经执行过 $count = $this->db->count("on_db_logs",[ "sql_name" => $name ]); if( $count >= 1 ) { $this->err_msg(-2000,$name."已经更新过!"); } $sql_name = $sql_dir.$name; //如果文件不存在,直接返回错误 if ( !file_exists($sql_name) ) { $this->err_msg(-2000,$name.'不存在!'); } //读取需要更新的SQL内容 try { //读取一个SQL文件,并将单个SQL文件拆分成单条SQL语句循环执行 switch ($name) { case '20220414.sql': $sql_content = explode("\n",file_get_contents($sql_name)); break; default: $sql_content = explode(';',file_get_contents($sql_name)); break; } //计算SQL总数 $num = count($sql_content) - 1; //初始数量设置为0 $init_num = 0; //遍历执行SQL语句 foreach ($sql_content as $sql) { //如果SQL为空,则跳过此次循环不执行 if( empty($sql) ) { continue; } $result = $this->db->query($sql); //只要单条SQL执行成功了就增加初始数量 if( $result ) { $init_num++; } } //无论最后结果如何,都将更新信息写入数据库 $insert_re = $this->db->insert("on_db_logs",[ "sql_name" => $name, "update_time" => time(), "status" => "TRUE" ]); if( $insert_re ) { $data = [ "code" => 0, "data" => $name."更新完成!总数${num},成功:${init_num}" ]; exit(json_encode($data)); } else { $this->err_msg(-2000,$name."更新失败,请人工检查!"); } } catch(Exception $e){ $this->err_msg(-2000,$e->getMessage()); } } /** * 更新option */ public function set_option($key,$value = '') { $key = htmlspecialchars(trim($key)); //如果key是空的 if( empty($key) ) { $this->err_msg(-2000,'键不能为空!'); } //鉴权 if( !$this->is_login() ) { $this->err_msg(-1002,'Authorization failure!'); } $count = $this->db->count("on_options", [ "key" => $key ]); //如果数量是0,则插入,否则就是更新 if( $count === 0 ) { try { $this->db->insert("on_options",[ "key" => $key, "value" => $value ]); $data = [ "code" => 0, "data" => "设置成功!" ]; exit(json_encode($data)); } catch (\Throwable $th) { $this->err_msg(-2000,$th); } } //更新数据 else if( $count === 1 ) { try { $this->db->update("on_options",[ "value" => $value ],[ "key" => $key ]); $data = [ "code" => 0, "data" => "设置已更新!" ]; exit(json_encode($data)); } catch (\Throwable $th) { $this->err_msg(-2000,$th); } } } /** * 更新option,返回BOOL值 */ protected function set_option_bool($key,$value = '') { $key = htmlspecialchars(trim($key)); //如果key是空的 if( empty($key) ) { return FALSE; } $count = $this->db->count("on_options", [ "key" => $key ]); //如果数量是0,则插入,否则就是更新 if( $count === 0 ) { try { $this->db->insert("on_options",[ "key" => $key, "value" => $value ]); $data = [ "code" => 0, "data" => "设置成功!" ]; return TRUE; } catch (\Throwable $th) { return FALSE; } } //更新数据 else if( $count === 1 ) { try { $this->db->update("on_options",[ "value" => $value ],[ "key" => $key ]); $data = [ "code" => 0, "data" => "设置已更新!" ]; return TRUE; } catch (\Throwable $th) { return FALSE; } } } }