使用PHP开发的简约导航/书签管理系统。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3044 lines
102 KiB

4 years ago
<?php
/**
* name:API核心类
12 months ago
* update:2024/01
4 years ago
* author:xiaoz<xiaoz93@outlook.com>
* blog:xiaoz.me
*/
2 years ago
//载入通用函数
require("./functions/helper.php");
3 years ago
define("API_URL","https://onenav.xiaoz.top");
4 years ago
class Api {
protected $db;
public function __construct($db){
1 year ago
// 修改默认获取模式为关联数组
$db->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
4 years ago
$this->db = $db;
//返回json类型
header('Content-Type:application/json; charset=utf-8');
}
/**
* name:创建分类目录
*/
3 years ago
public function add_category($token,$name,$property = 0,$weight = 0,$description = '',$font_icon = '',$fid = 0){
4 years ago
$this->auth($token);
3 years ago
//分类名称不允许为空
if( empty($name) ) {
$this->err_msg(-2000,'分类名称不能为空!');
}
4 years ago
$data = [
3 years ago
'name' => htmlspecialchars($name,ENT_QUOTES),
4 years ago
'add_time' => time(),
'weight' => $weight,
'property' => $property,
3 years ago
'description' => htmlspecialchars($description,ENT_QUOTES),
3 years ago
'font_icon' => $font_icon,
'fid' => $fid
4 years ago
];
//插入分类目录
$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));
}
}
/**
* 修改分类目录
*
*/
3 years ago
public function edit_category($token,$id,$name,$property = 0,$weight = 0,$description = '',$font_icon = '',$fid = 0){
4 years ago
$this->auth($token);
3 years ago
$fid = intval($fid);
4 years ago
//如果id为空
if( empty($id) ){
$this->err_msg(-1003,'The category ID cannot be empty!');
}
3 years ago
//根据fid查询这个分类是否存在
$count = $this->db->count("on_categorys", [
"id" => $fid
]);
//如果fid不是0,且查询结果小于1,则认为这个父级ID是不存在的,则不允许修改
if( !empty($fid) && ($count < 1) ) {
$this->err_msg(-2000,'父级ID不存在!');
}
//查询fid是否是二级分类的ID,如果是,则不允许修改
$category = $this->db->get("on_categorys","*",[
"id" => $fid
]);
//如果查询到他的父ID不是0,则是一个二级分类
if( intval($category['fid']) !== 0 ) {
$this->err_msg(-2000,'父分类不能是二级分类!');
}
4 years ago
//如果分类名为空
3 years ago
elseif( empty($name ) ){
4 years ago
$this->err_msg(-1004,'The category name cannot be empty!');
}
//更新数据库
else{
3 years ago
//根据分类ID查询改分类下面是否已经存在子分类,如果存在子分类了则不允许设置为子分类,实用情况:一级分类下存在二级分类,无法再将改一级分类修改为二级分类
$count = $this->db->count("on_categorys", [
"fid" => $id
]);
3 years ago
//该分类下的子分类数量大于0,并且父级ID修改为其它分类
3 years ago
if( ( $count > 0 ) && ( $fid !== 0 ) ) {
3 years ago
$this->err_msg(-2000,'修改失败,该分类下已存在子分类!');
}
4 years ago
$data = [
3 years ago
'name' => htmlspecialchars($name,ENT_QUOTES),
4 years ago
'up_time' => time(),
'weight' => $weight,
'property' => $property,
3 years ago
'description' => htmlspecialchars($description,ENT_QUOTES),
3 years ago
'font_icon' => $font_icon,
'fid' => $fid
4 years ago
];
$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)
*
*/
3 years ago
public function err_msg($code,$err_msg){
4 years ago
$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){
1 year ago
// 当方法没有传递token的时候,则先尝试通过POST/GET获取token
if( empty($token) ) {
$token = empty( $_POST['token'] ) ? $_GET['token'] : $_POST['token'];
}
4 years ago
//计算正确的token:用户名 + TOKEN
3 years ago
$SecretKey = @$this->db->get('on_options','*',[ 'key' => 'SecretKey' ])['value'];
$token_yes = md5(USER.$SecretKey);
2 years ago
//获取header中的X-token
$xtoken = $_SERVER['HTTP_X_TOKEN'];
2 years ago
//如果通过header传递token,且验证通过
if( !empty($xtoken) && ($xtoken === $token_yes) ) {
2 years ago
return TRUE;
}
4 years ago
//如果token为空,则验证cookie
if(empty($token)) {
if( !$this->is_login() ) {
$this->err_msg(-1002,'Authorization failure!');
}
3 years ago
else if( $this->is_login() ){
return TRUE;
}
else{
$this->err_msg(-1002,'Cookie authorization failure!');
}
4 years ago
}
3 years ago
else if ( empty($SecretKey) ) {
$this->err_msg(-2000,'请先生成SecretKey!');
}
4 years ago
else if($token != $token_yes){
$this->err_msg(-1002,'Authorization failure!');
}
else{
2 years ago
return TRUE;
4 years ago
}
}
/**
* name:添加链接
*/
2 years ago
public function add_link($token,$fid,$title,$url,$description = '',$weight = 0,$property = 0,$url_standby = '',$font_icon = ''){
4 years ago
$this->auth($token);
$fid = intval($fid);
//检测链接是否合法
3 years ago
//$this->check_link($fid,$title,$url);
$this->check_link([
'fid' => $fid,
'title' => $title,
'url' => $url,
'url_standby' => $url_standby
]);
4 years ago
//合并数据
$data = [
'fid' => $fid,
3 years ago
'title' => htmlspecialchars($title,ENT_QUOTES),
2 years ago
'url' => $url,
'url_standby' => $url_standby,
3 years ago
'description' => htmlspecialchars($description,ENT_QUOTES),
4 years ago
'add_time' => time(),
'weight' => $weight,
2 years ago
'property' => $property
4 years ago
];
2 years ago
//如果$font_icon不为空,才一起追加写入数据库
if( !empty($font_icon) ) {
$data['font_icon'] = $font_icon;
}
4 years ago
//插入数据库
$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!');
}
}
3 years ago
/**
* 批量修改链接分类
*/
public function batch_modify_category($data) {
$this->auth($token);
//获取链接ID,是一个数组
$id = implode(',',$data['id']);
//获取分类ID
$fid = $data['fid'];
//查询分类ID是否存在
$count = $this->db->count('on_categorys',[ 'id' => $fid]);
//如果分类ID不存在
if( empty($fid) || empty($count) ) {
$this->err_msg(-2000,'分类ID不存在!');
}
else{
$sql = "UPDATE on_links SET fid='$fid' WHERE id IN ($id)";
$re = $this->db->query($sql);
if( $re ) {
$id = $this->db->id();
$data = [
'code' => 0,
'msg' => "success"
];
exit(json_encode($data));
}
else{
$this->err_msg(-2000,'更新失败!');
}
}
}
3 years ago
/**
* 批量修改链接属性为公有或私有
*/
public function set_link_attribute($data) {
$this->auth($token);
//获取链接ID,是一个数组
$ids = implode(',',$data['ids']);
$property = intval($data['property']);
//拼接SQL文件
$sql = "UPDATE on_links SET property = $property WHERE id IN ($ids)";
$re = $this->db->query($sql);
//返回影响行数
$row = $re->rowCount();
if ( $row > 0 ){
$this->return_json(200,"success");
}
else{
$this->return_json(-2000,"failed");
}
}
2 years ago
/**
* name:分类批量设置为私有或公有
*
*/
public function set_cat_batch($data) {
$this->auth($token);
//获取链接ID,是一个数组
$ids = implode(',',$data['ids']);
$property = intval($data['property']);
//拼接SQL文件
$sql = "UPDATE on_categorys SET property = $property WHERE id IN ($ids)";
// echo $sql;
$re = $this->db->query($sql);
//返回影响行数
$row = $re->rowCount();
if ( $row > 0 ){
$this->return_json(200,"success");
}
else{
$this->return_json(-2000,"failed");
}
}
3 years ago
4 years ago
/**
* 批量导入链接
*/
public function imp_link($token,$filename,$fid,$property = 0){
3 years ago
//过滤$filename
$filename = str_replace('../','',$filename);
$filename = str_replace('./','',$filename);
12 months ago
// 获取文件名称的后缀
$suffix = explode('.',$filename);
// 如果没有后缀,则不合法,通过数组长度判断后缀
if( count($suffix) < 2 ) {
$this->err_msg(-2000,'文件不合法!');
}
// 获取文件后缀
$suffix = strtolower(end($suffix));
if( ( $suffix != 'html' ) && ( $suffix != 'htm' ) ) {
$this->err_msg(-2000,'文件不合法!');
}
4 years ago
$this->auth($token);
//检查文件是否存在
if ( !file_exists($filename) ) {
$this->err_msg(-1016,'File does not exist!');
}
//解析HTML数据
$content = file_get_contents($filename);
$pattern = "/<A.*<\/A>/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('</A','',$title);
//如果标题或者链接为空,则不导入
if( ($title == '') || ($url == '') ) {
$fail++;
continue;
}
$data = [
'fid' => $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));
}
3 years ago
/**
* 批量导入链接并自动创建分类,这是新的导入接口
*/
public function import_link($filename,$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);
$HTMLs = explode("\n",$content);//分割文本
$data = []; //链接组
$categorys = []; //分类信息组
$categoryt = []; //分类信息表
// 遍历HTML
foreach( $HTMLs as $HTMLh ){
//匹配分类名称
if( preg_match("/<DT><H3.+>(.*)<\/H3>/i",$HTMLh,$category) ){
//匹配到文件夹名时加入数组
array_push($categoryt,$category[1]);
array_push($categorys,$category[1]);
}elseif( preg_match('/<\/DL><p>/i',$HTMLh) ){
//匹配到文件夹结束标记时删除一个
array_pop($categorys);
}elseif( preg_match('/<DT><A HREF="(.+)" ADD_DAT.+>(.+)<\/A>/i',$HTMLh,$urls) ){
$datat['category'] = $categorys[count($categorys) -1];
$datat['title'] = $urls[2];
$datat['url'] = $urls[1];
array_push($data,$datat);
}
}
$categoryt = array_unique($categoryt);
3 years ago
//追加一个默认分类,用来存储部分链接找不到分类的情况
array_push($categoryt,"默认分类");
3 years ago
3 years ago
3 years ago
//批量创建分类
$this->batch_create_category($categoryt);
//查询所有分类
$categorys = $this->db->select("on_categorys",[
"name",
"id",
"fid"
]);
// var_dump($categorys);
// exit;
//链接计数
$i = 0;
//统计链接总数
$count = count($data);
//批量导入链接
foreach ($data as $key => $value) {
$category_name = trim($value['category']);
3 years ago
//如果链接的分类是空的,则设置为默认分类
3 years ago
$category_name = empty( $category_name ) ? "默认分类" : $category_name;
3 years ago
foreach ($categorys as $category) {
if( trim( $category['name'] ) == $category_name ) {
$fid = intval($category['id']);
break;
}
}
3 years ago
3 years ago
//合并数据
$link_data = [
'fid' => $fid,
'title' => htmlspecialchars($value['title']),
'url' => htmlspecialchars($value['url'],ENT_QUOTES),
'add_time' => time(),
'weight' => 0,
'property' => $property
];
3 years ago
3 years ago
//插入数据库
$re = $this->db->insert('on_links',$link_data);
//返回影响行数
$row = $re->rowCount();
if ($row) {
$i++;
}
}
//删除书签文件
unlink($filename);
$this->return_json(200,"success",[
"count" => $count,
"success" => $i,
"failed" => $count - $i
]);
}
/**
* 批量创建分类
* 接收一个一维数组
*/
protected function batch_create_category($category_name) {
$i = 0;
foreach ($category_name as $key => $value) {
3 years ago
$value = empty($value) ? "默认分类" : $value;
3 years ago
$data = [
'name' => trim($value),
'add_time' => time(),
'weight' => 0,
'property' => 1,
'description' => "书签导入时自动创建",
'fid' => 0
];
try {
//插入分类目录
$this->db->insert("on_categorys",$data);
$i++;
} catch (\Throwable $th) {
continue;
}
}
return $i;
}
4 years ago
/**
* 书签上传
* 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));
}
}
}
1 year ago
/**
* name:通用上传接口
* @param1:指定上传路径
* @param2:指定允许的后缀名称,是一个数组
*/
public function general_upload($path,$suffixs){
// 验证权限
$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"];
// 遍历$suffixs后缀文件,判断是否允许
foreach ($suffixs as $key => $value) {
if( $suffix == $value ) {
$allow = true;
break;
}
}
// 如果是不允许的文件,则删除
if( $allow !== TRUE ) {
//删除临时文件
unlink($filename);
$this->err_msg(-1014,'Unsupported file suffix name!');
}
// 如果是允许的文件,则移动到指定目录,path格式为data/
if( copy($temp,$path.$filename) ) {
$data = [
'code' => 0,
'file_name' => $path.$filename
];
exit(json_encode($data));
}
else{
// 复制文件失败了
$this->err_msg(-2000,'上传失败,请检查' + $path + '目录权限!');
}
}
}
/**
* 图标上传
* type:上传类型
*/
2 years ago
public function uploadImages($token){
$this->auth($token);
2 years ago
//获取icon名称
$icon_name = $_POST['icon_name'];
//获取老文件名称,然后删除
$old_pic = $_POST['old_pic'];
//如果老文件名称合法,则删除
$pattern = "/^data\/upload\/[0-9]+\/[0-9a-zA-Z]+\.(jpg|jpeg|png|bmp|gif|svg)$/";
//如果名称不合法,则终止执行
if( preg_match($pattern,$old_pic) ){
@unlink($old_pic);
}
//如果名称是空的
if( empty($icon_name) ) {
$this->return_json(-2000,'','获取图标名称失败!');
}
if ($_FILES["file"]["error"] > 0)
{
2 years ago
//$this->err_msg(-1015,'File upload failed!');
$this->return_json(-2000,'','File upload failed!');
}
else
{
//根据时间生成文件名
$filename = $_FILES["file"]["name"];
//获取文件后缀
$suffix = explode('.',$filename);
$suffix = strtolower(end($suffix));
//临时文件位置
$temp = $_FILES["file"]["tmp_name"];
2 years ago
if( $suffix != 'ico' && $suffix != 'jpg' && $suffix != 'jpeg' && $suffix != 'png' && $suffix != 'bmp' && $suffix != 'gif' && $suffix != 'svg' ) {
//删除临时文件
2 years ago
@unlink($filename);
@unlink($temp);
$this->return_json(-2000,'','Unsupported file suffix name!');
}
2 years ago
//上传路径,格式为data/upload/202212/1669689755.png
$upload_path = "data/upload/".date( "Ym", time() ).'/'.$icon_name.'.'.$suffix;
//如果目录不存在,则创建
$upload_dir = dirname($upload_path);
if( !is_dir( $upload_dir ) ) {
//递归创建目录
mkdir($upload_dir,0755,true);
}
//$newfilename = 'upload/'.time().'.'.$suffix;
//移动临时文件到指定上传路径
if( move_uploaded_file($temp,$upload_path) ) {
$data = [
2 years ago
'file_name' => $upload_path
];
2 years ago
$this->return_json(200,$data,'success');
}
else{
$this->return_json(-2000,'','上传失败,请检查目录权限!');
}
}
}
3 years ago
/**
* 导出HTML链接进行备份
*/
public function export_link(){
//鉴权
$this->auth($token);
//查询所有分类
$categorys = $this->db->select("on_categorys","*");
//定义一个空数组用来存储查询后的数据
$data = [];
//遍历分类
foreach ($categorys as $key => $category) {
//查询该分类下的所有链接
$links = $this->db->select("on_links","*",[
"fid" => $category['id']
]);
// echo $category['name'];
// var_dump($links);
// exit;
//组合为一个一维数组
$arr[$category['name']] = $links;
// var_dump();
// exit;
$data[$category['name']] = $arr[$category['name']];
//清除临时数据
unset($arr);
}
//返回数据
return $data;
}
4 years ago
/**
* name:修改链接
*/
public function edit_link($token,$id,$fid,$title,$url,$description = '',$weight = 0,$property = 0,$url_standby = '',$font_icon = ''){
4 years ago
$this->auth($token);
$fid = intval($fid);
2 years ago
/**
* name:获取更新类型
* description:主要是因为兼容部分之前老的接口,老的接口不用变动,只能从OneNav后台添加图标,因此增加type判断是否是OneNav后台
* console:指从OneNav后台进行更新
*/
$type = trim($_GET['type']);
4 years ago
//检测链接是否合法
3 years ago
//$this->check_link($fid,$title,$url);
$this->check_link([
'fid' => $fid,
3 years ago
'title' => htmlspecialchars($title,ENT_QUOTES),
2 years ago
'url' => $url,
'url_standby' => $url_standby
3 years ago
]);
4 years ago
//查询ID是否存在
$count = $this->db->count('on_links',[ 'id' => $id]);
//如果id不存在
if( (empty($id)) || ($count == false) ) {
$this->err_msg(-1012,'link id not exists!');
4 years ago
}
//合并数据
$data = [
'fid' => $fid,
3 years ago
'title' => htmlspecialchars($title,ENT_QUOTES),
4 years ago
'url' => $url,
3 years ago
'url_standby' => $url_standby,
3 years ago
'description' => htmlspecialchars($description,ENT_QUOTES),
4 years ago
'up_time' => time(),
'weight' => $weight,
2 years ago
'property' => $property
4 years ago
];
2 years ago
if( !empty($font_icon) ) {
$data['font_icon'] = $font_icon;
}
//如果是从OneNav后台更新,则无论如何都要加上font_icon
if( $type === 'console' ) {
$data['font_icon'] = $font_icon;
}
4 years ago
//插入数据库
$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!');
}
}
2 years ago
/**
* name:单行链接修改
*/
public function edit_link_row(){
//验证授权
$this->auth($token);
// 获取POST请求中的JSON数据
$json_data = file_get_contents('php://input');
// 解析JSON数据为PHP对象
$obj = json_decode($json_data);
$id = intval($obj->id);
$fid = intval($obj->fid);
//查询ID是否存在
$count = $this->db->count('on_links',[ 'id' => $id]);
//如果id不存在
if( (empty($id)) || ($count == false) ) {
$this->err_msg(-1012,'link id not exists!');
}
// 拼接需要更新的数据
$data = [
'title' => trim($obj->title),
'weight' => intval($obj->weight)
];
//插入数据库
$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!');
}
}
4 years ago
4 years ago
/**
* 删除链接
*/
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!');
}
}
}
/**
* 验证链接合法性
3 years ago
* 接收一个数组作为参数
4 years ago
*/
3 years ago
protected function check_link($data){
$fid = $data['fid'];
$title = $data['title'];
$url = $data['url'];
$url_standby = @$data['url_standby'];
4 years ago
//如果父及(分类)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!');
}
3 years ago
//通过正则匹配链接是否合法,支持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) ) {
4 years ago
$this->err_msg(-1010,'URL is not valid!');
}
3 years ago
//备用链接不合法
3 years ago
if ( ( !empty($url_standby) ) && ( !preg_match($pattern, $url_standby) ) ) {
3 years ago
$this->err_msg(-1010,'URL is not valid!');
}
4 years ago
return true;
}
/**
* 查询分类目录
*/
public function category_list($page,$limit){
3 years ago
$token = @$_POST['token'];
4 years ago
$offset = ($page - 1) * $limit;
2 years ago
1 month ago
// 验证登录或 token
if ($this->is_login() || (!empty($token) && $this->auth($token))) {
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = a.fid LIMIT 1) AS fname,(SELECT COUNT(id) FROM on_links WHERE fid = a.id) AS link_num FROM on_categorys as a ORDER BY fid ASC, weight DESC, id ASC";
$count = $this->db->count('on_categorys', '*');
} elseif (!empty($token)) {
3 years ago
$this->auth($token);
1 month ago
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = a.fid LIMIT 1) AS fname,(SELECT COUNT(id) FROM on_links WHERE fid = a.id) AS link_num FROM on_categorys as a ORDER BY fid ASC, weight DESC, id ASC";
$count = $this->db->count('on_categorys', '*');
} else {
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = a.fid LIMIT 1) AS fname,(SELECT COUNT(id) FROM on_links WHERE fid = a.id) AS link_num FROM on_categorys as a WHERE property = 0 ORDER BY fid ASC, weight DESC, id ASC";
$count = $this->db->count('on_categorys', '*', ['property' => 0]);
4 years ago
}
1 month ago
$all_categories = $this->db->query($sql)->fetchAll();
// 构建排序规则
$sorted_categories = [];
$categories_by_fid = [];
foreach ($all_categories as $category) {
if ($category['fid'] == 0) {
// 父级分类
$sorted_categories[] = $category;
} else {
// 子分类,按 fid 分组
$categories_by_fid[$category['fid']][] = $category;
}
4 years ago
}
1 month ago
// 对每个子分类组按 weight 排序
foreach ($categories_by_fid as &$sub_categories) {
usort($sub_categories, function ($a, $b) {
return $b['weight'] <=> $a['weight'];
});
}
// 将子分类插入到对应父级分类后
$final_sorted_categories = [];
foreach ($sorted_categories as $parent_category) {
$final_sorted_categories[] = $parent_category;
if (isset($categories_by_fid[$parent_category['id']])) {
$final_sorted_categories = array_merge($final_sorted_categories, $categories_by_fid[$parent_category['id']]);
}
}
// 截取分页数据
$paged_data = array_slice($final_sorted_categories, $offset, $limit);
$result = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $paged_data,
4 years ago
];
1 month ago
exit(json_encode($result));
4 years ago
}
3 years ago
/**
* 生成
*/
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生成失败!');
}
}
4 years ago
/**
* 查询链接
3 years ago
* 接收一个数组作为参数
4 years ago
*/
3 years ago
public function link_list($data){
$limit = $data['limit'];
$token = $data['token'];
$offset = ($data['page'] - 1) * $data['limit'];
3 years ago
//$fid = @$data['category_id'];
$count = $this->db->count('on_links','*');
3 years ago
2 years ago
//如果成功登录,但token为空,获取所有
2 years ago
if( $this->is_login() || ( !empty($token) && $this->auth($token) ) ){
3 years ago
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links 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 ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
3 years ago
}
2 years ago
//如果通过header传递的token验证成功,则获取所有
2 years ago
// else if( $this->auth("") === TRUE ) {
// $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
// }
3 years ago
//如果即没有登录成功,又没有token,则默认为游客,游客查询链接属性为公有,分类为公有,不查询私有
3 years ago
else{
3 years ago
$c_sql = "SELECT COUNT(*) AS num FROM on_links WHERE property = 0 AND fid IN (SELECT id FROM on_categorys WHERE property = 0)";
$count = $this->db->query($c_sql)->fetchAll()[0]['num'];
$count = intval($count);
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links WHERE property = 0 AND fid IN (SELECT id FROM on_categorys WHERE property = 0) ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
3 years ago
}
3 years ago
//原生查询
$datas = $this->db->query($sql)->fetchAll();
$datas = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $datas
];
exit(json_encode($datas));
}
/**
* 查询某个分类下面的链接
* 接收一个数组作为参数
*/
public function q_category_link($data){
$limit = $data['limit'];
$token = $data['token'];
$offset = ($data['page'] - 1) * $data['limit'];
$fid = @$data['category_id'];
//$fid = @$data['category_id'];
$count = $this->db->count('on_links','*',[
'fid' => $fid
]);
//如果FID是空的,则直接终止
if( empty($fid) ) {
$datas = [
'code' => -2000,
'msg' => '分类ID不能为空!',
'count' => 0,
'data' => []
];
exit(json_encode($datas));
}
4 years ago
//如果成功登录,但token为空
if( ($this->is_login()) && (empty($token)) ){
3 years ago
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links WHERE fid = $fid ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
4 years ago
}
2 years ago
//通过header获取token成功
2 years ago
// else if( $this->auth("") ) {
// $sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links WHERE fid = $fid ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
// }
4 years ago
//如果token验证通过
elseif( (!empty($token)) && ($this->auth($token)) ) {
3 years ago
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links WHERE fid = $fid ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
4 years ago
}
3 years ago
//如果即没有登录成功,又没有token,则默认为游客,游客查询链接属性为公有,分类为公有,不查询私有
4 years ago
else{
3 years ago
$c_sql = "SELECT COUNT(*) AS num FROM on_links WHERE property = 0 AND fid = $fid";
$count = $this->db->query($c_sql)->fetchAll()[0]['num'];
$count = intval($count);
3 years ago
2 years ago
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_links.fid) AS category_name FROM on_links WHERE property = 0 AND fid = $fid ORDER BY weight DESC,id DESC LIMIT {$limit} OFFSET {$offset}";
4 years ago
}
3 years ago
4 years ago
//原生查询
$datas = $this->db->query($sql)->fetchAll();
$datas = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $datas
];
exit(json_encode($datas));
}
/**
3 years ago
* 查询单个链接
* 此函数接收一个数组
*/
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" ) {
3 years ago
//链接是公开的,但是分类是私有的,则不显示
$category_property = $this->db->get("on_categorys","property",[
"id" => $link_info['fid']
]);
$category_property = intval($category_property);
//分类属性为1,则说明是私有链接,则未认证用户不允许查询
if( $category_property === 1 ){
//进行认证
$this->auth($token);
}
3 years ago
$datas = [
'code' => 0,
'data' => $link_info
];
}
//如果是私有链接,并且认证通过
elseif( $link_info['property'] == "1" ) {
3 years ago
if ( ( $this->auth($token) ) || ( $this->is_login() ) ) {
3 years ago
$datas = [
'code' => 0,
'data' => $link_info
];
}
//exit(json_encode($datas));
}
//如果是其它情况,则显示为空
else{
$datas = [
'code' => 0,
'data' => []
];
//exit(json_encode($datas));
}
exit(json_encode($datas));
}
3 years ago
/**
* 查询单个分类信息
* 此函数接收一个数组
*/
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));
}
3 years ago
/**
4 years ago
* 验证是否登录
*/
protected function is_login(){
2 weeks ago
$key = md5(USER.ENCRYPTED_PASSWORD.'onenav'.$_SERVER['HTTP_USER_AGENT']);
4 years ago
//获取session
$session = $_COOKIE['key'];
//如果已经成功登录
if($session == $key) {
return true;
}
else{
return false;
}
}
/**
* 获取链接信息
*/
public function get_link_info($token,$url){
$this->auth($token);
//检查链接是否合法
3 years ago
$pattern = "/^(http:\/\/|https:\/\/).*/";
4 years ago
//链接不合法
3 years ago
if( empty($url) ) {
$this->err_msg(-2000,'URL不能为空!');
}
if( !preg_match($pattern,$url) ){
3 years ago
$this->err_msg(-1010,'只支持识别http/https协议的链接!');
4 years ago
}
3 years ago
else if( !filter_var($url, FILTER_VALIDATE_URL) ) {
$this->err_msg(-2000,'只支持识别http/https协议的链接!');
}
4 years ago
//获取网站标题
$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>(.*)<\/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));
}
4 years ago
/**
* 自定义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());
}
}
4 years ago
/**
* 获取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;
}
3 years ago
/**
* name:检查弱密码
*/
public function check_weak_password($token){
$this->auth($token);
//如果用户名、密码为初始密码,则提示修改
if ( ( USER == 'xiaoz' ) && ( PASSWORD == 'xiaoz.me' ) ) {
$this->err_msg(-1,'Weak password!');
}
}
3 years ago
/**
* 获取SQL更新列表
* 循环读取db/sql/目录下的.sql文件
*/
public function get_sql_update_list($data) {
3 years ago
//鉴权
if( !$this->is_login() ) {
$this->err_msg(-1002,'Authorization failure!');
}
//待更新的数据库文件目录
3 years ago
$sql_dir = 'db/sql/';
3 years ago
//待更新的sql文件列表,默认为空
3 years ago
$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,
3 years ago
"data" => ['on_db_logs.sql']
3 years ago
];
exit(json_encode($data));
3 years ago
}else{
3 years ago
//如果不为0,则需要查询数据库更新表里面的数据进行差集比对
3 years ago
$get_on_db_logs = $this->db->select("on_db_logs",[
"sql_name"
],[
"status" => "TRUE"
]);
3 years ago
//声明一个空数组,存储已更新的数据库列表
3 years ago
$already_dbs = [];
3 years ago
foreach ($get_on_db_logs as $value) {
3 years ago
array_push($already_dbs,$value['sql_name']);
}
3 years ago
3 years ago
//array_diff() 函数返回两个数组的差集数组
$diff_result = array_diff($sql_files_all,$already_dbs);
3 years ago
//去掉键
$diff_result = array_values($diff_result);
sort($diff_result);
3 years ago
$data = [
"code" => 0,
"data" => $diff_result
];
exit(json_encode($data));
3 years ago
}
3 years ago
3 years ago
}
/**
* 执行SQL更新语句,只执行单条更新
*/
public function exe_sql($data) {
3 years ago
//鉴权
if( !$this->is_login() ) {
$this->err_msg(-1002,'Authorization failure!');
}
3 years ago
//数据库sql目录
$sql_dir = 'db/sql/';
$name = $data['name'];
3 years ago
//查询sql是否已经执行过
$count = $this->db->count("on_db_logs",[
"sql_name" => $name
]);
if( $count >= 1 ) {
$this->err_msg(-2000,$name."已经更新过!");
}
3 years ago
$sql_name = $sql_dir.$name;
3 years ago
//如果文件不存在,直接返回错误
if ( !file_exists($sql_name) ) {
3 years ago
$this->err_msg(-2000,$name.'不存在!');
3 years ago
}
//读取需要更新的SQL内容
try {
3 years ago
//读取一个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;
}
3 years ago
//计算SQL总数
$num = count($sql_content) - 1;
//初始数量设置为0
$init_num = 0;
//遍历执行SQL语句
foreach ($sql_content as $sql) {
//如果SQL为空,则跳过此次循环不执行
if( empty($sql) ) {
continue;
3 years ago
}
3 years ago
$result = $this->db->query($sql);
//只要单条SQL执行成功了就增加初始数量
if( $result ) {
$init_num++;
3 years ago
}
3 years ago
}
3 years ago
//无论最后结果如何,都将更新信息写入数据库
$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 {
3 years ago
$this->err_msg(-2000,$name."更新失败,请人工检查!");
3 years ago
}
3 years ago
3 years ago
} catch(Exception $e){
$this->err_msg(-2000,$e->getMessage());
}
}
3 years ago
/**
* 保存主题参数
*/
public function save_theme_config($data) {
$this->auth($token);
//获取主题名称
$name = $data['name'];
//获取config参数,是一个对象
$config = $data['config'];
//获取主题配置文件config.json
if ( is_dir("templates/".$name) ) {
$config_file = "templates/".$name."/config.json";
}
else{
$config_file = "data/templates/".$name."/config.json";
}
$config_content = json_encode($config,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
//写入配置
try {
$re = @file_put_contents($config_file,$config_content);
$this->return_json(0,"success");
} catch (\Throwable $th) {
$this->err_msg(-2000,"写入配置失败!");
}
}
3 years ago
/**
* 删除主题
*/
public function delete_theme($name) {
//验证授权
$this->auth($token);
//正则判断主题名称是否合法
$pattern = "/^[a-zA-Z0-9][a-zA-Z0-9-_]+[a-zA-Z0-9]$/";
if ( !preg_match($pattern,$name) ) {
$this->return_json(-2000,'',"主题名称不合法!");
}
//如果是默认主题,则不允许删除
1 month ago
if( ($name === 'default') || ($name === 'admin') || ($name === 'default2') ) {
3 years ago
$this->return_json(-2000,'',"默认主题不允许删除!");
}
//查询当前使用中的主题
$current_theme = $this->db->get('on_options','value',[ 'key' => "theme" ]);
//如果是当前使用中的主题也不允许删除
if ( $current_theme == $name ) {
$this->return_json(-2000,'',"使用中的主题不允许删除!");
}
//删除主题
$this->deldir("templates/".$name);
$this->deldir("data/templates/".$name);
//判断主题文件夹是否还存在
if( is_dir("templates/".$name) || is_dir("data/templates/".$name) ) {
$this->return_json(-2000,'',"删除失败,可能是权限不足!");
}
else{
$this->return_json(200,'',"主题已删除!");
}
}
/**
* 删除一个目录
*/
protected function deldir($dir) {
//先删除目录下的文件:
$dh=opendir($dir);
while ($file=readdir($dh)) {
if($file!="." && $file!="..") {
$fullpath=$dir."/".$file;
if(!is_dir($fullpath)) {
unlink($fullpath);
} else {
$this->deldir($fullpath);
}
}
}
closedir($dh);
//删除当前文件夹:
if(rmdir($dir)) {
return true;
} else {
return false;
}
}
3 years ago
/**
* 获取主题参数
*/
public function get_theme_config() {
$template = $this->db->get("on_options","value",[
"key" => "theme"
]);
//获取主题配置信息
//获取主题配置
if( file_exists("templates/".$template."/config.json") ) {
$config_file = "templates/".$template."/config.json";
}
3 years ago
else if( file_exists("data/templates/".$template."/config.json") ) {
3 years ago
$config_file = "data/templates/".$template."/config.json";
}
else if( file_exists("templates/".$template."/info.json") ) {
$config_file = "templates/".$template."/info.json";
}
else {
$config_file = "data/templates/".$template."/info.json";
}
//读取主题配置
$config_content = @file_get_contents($config_file);
//如果是info.json,则特殊处理下
if ( strstr($config_file,"info.json") ) {
3 years ago
$config_content = json_decode($config_content);
3 years ago
$theme_config = $config_content->config;
}
else{
$theme_config = $config_content;
3 years ago
$theme_config = json_decode($theme_config);
3 years ago
}
3 years ago
3 years ago
$this->return_json(200,$theme_config,"");
}
/**
* 通用json消息返回
*/
public function return_json($code,$data,$msg = "") {
$return = [
"code" => intval($code),
"data" => $data,
"msg" => $msg
];
exit(json_encode($return));
}
3 years ago
/**
* 更新option
*/
public function set_option($key,$value = '') {
1 week ago
// 验证授权
$this->auth($token);
3 years ago
$key = htmlspecialchars(trim($key));
//如果key是空的
if( empty($key) ) {
$this->err_msg(-2000,'键不能为空!');
}
1 week ago
3 years ago
$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);
}
}
}
1 week ago
/**
* 新的设置接口
*/
public function new_set_option(){
// 验证授权
$this->auth($token);
// 获取key
$key = trim(@$_POST['key']);
// 获取value
$value = trim(@$_POST['value']);
$key = htmlspecialchars(trim($key));
//如果key是空的
if( empty($key) ) {
$this->return_json(-2000,'','key不能为空!');
}
$count = $this->db->count("on_options", [
"key" => $key
]);
//如果数量是0,则插入,否则就是更新
if( $count === 0 ) {
try {
$this->db->insert("on_options",[
"key" => $key,
"value" => $value
]);
$this->return_json(200,'','设置成功!');
} catch (\Throwable $th) {
$this->return_json(-2000,'','设置失败!');
}
}
//更新数据
else if( $count === 1 ) {
try {
$this->db->update("on_options",[
"value" => $value
],[
"key" => $key
]);
$this->return_json(200,'','设置已更新!');
} catch (\Throwable $th) {
$this->return_json(-2000,'','设置失败!');
}
}
}
3 years ago
/**
* 更新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;
}
}
}
3 years ago
/**
* 用户状态
*/
3 years ago
public function check_login($token){
$re = $this->auth($token);
if( $re ) {
$this->return_json(200,"true","success");
}
3 years ago
}
3 years ago
/**
* 验证订阅是否有效
*/
public function check_subscribe() {
//验证token是否合法
$this->auth($token);
//获取订阅信息
//获取当前站点信息
$subscribe = $this->db->get('on_options','value',[ 'key' => "s_subscribe" ]);
$domain = $_SERVER['HTTP_HOST'];
$subscribe = unserialize($subscribe);
//api请求地址
$api_url = API_URL."/v1/check_subscribe.php?order_id=".$subscribe['order_id']."&email=".$subscribe['email']."&domain=".$domain;
9 months ago
// 如果邮箱或者订单号为空,则返回提示
if( empty($subscribe['order_id']) || empty($subscribe['email']) ) {
$this->return_json(-2000,'','此功能需要订阅!');
}
3 years ago
try {
#GET HTTPS
$curl = curl_init($api_url);
#设置useragent
3 years ago
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36");
3 years ago
curl_setopt($curl, CURLOPT_FAILONERROR, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
#设置超时时间,最小为1s(可选)
3 years ago
curl_setopt($curl , CURLOPT_TIMEOUT, 30);
3 years ago
$html = curl_exec($curl);
curl_close($curl);
//解析json
$data = json_decode($html);
//var_dump($data->data->end_time);
//echo strtotime($data->data->end_time);
//var_dump($data->code);
//如果状态码返回200,并且订阅没有到期
if( (intval($data->code) === 200) && ( $data->data->end_time > ( strtotime( date("Y-m-d",time()) ) )) ) {
$this->return_json(200,$data->data,'success');
}
else if( intval($data->code === -1000 ) ) {
$this->return_json(-2000,'',$data->msg);
}
else{
3 years ago
$this->return_json(-2000,'',"请求接口失败,请重试!");
3 years ago
}
} catch (\Throwable $th) {
3 years ago
$this->return_json(-2000,'','网络请求失败,请重试!');
3 years ago
}
}
3 years ago
/**
* 下载主题
*/
public function down_theme($data) {
//主题名称
$name = $data['name'];
//key-value
$key = $data['key'];
$value = $data['value'];
//拼接主题URL
$url = API_URL."/v1/down_theme.php?name=${name}&key=${key}&value=${value}";
//验证token是否合法
$this->auth($token);
//检查主题是否已经存在
if ( $data['type'] == 'download' ) {
$theme1 = "templates/".$name;
$theme2 = "data/templates/".$name;
if( is_dir($theme1) || is_dir($theme2) ) {
$this->return_json(-2000,'','主题已存在,无需重复下载!');
}
}
//如果返回404状态
$res = get_headers($url,1);
if( strstr($res[0],'404') ) {
$this->return_json(-2000,'','远程服务器上不存在此主题!');
}
//判断主题目录是否存在,如果curl_host是alpine,则视为容器,容器则将主题目录设置为data/templates
$curl_host = curl_version()['host'];
if( strstr($curl_host,'alpine') ) {
4 weeks ago
// 默认主题一律保存到templates目录
if( $name == "default2" ) {
$theme_dir = "templates";
}
else{
$theme_dir = "data/templates";
}
3 years ago
}
else{
$theme_dir = "templates";
}
//主题完整压缩包路径
$file_name = $theme_dir."/${name}.tar.gz";
if( !is_dir($theme_dir) ) {
mkdir($theme_dir,0755);
}
//尝试下载主题
try {
//下载主题,并设置超时时间为120s
$content = $this->curl_get($url,120);
//写入主题
$re = file_put_contents($theme_dir."/${name}.tar.gz",$content);
//如果写入主题失败了,说明权限不粗糙
if( !$re ) {
$this->return_json(-2000,'','主题写入失败,请检查目录权限!');
}
else{
//解压文件
$phar = new PharData($file_name);
//路径 要解压的文件 是否覆盖
$phar->extractTo($theme_dir."/${name}", null, true);
//删除主题
unlink($file_name);
$this->return_json(200,'','主题下载成功!');
}
} catch (\Throwable $th) {
$this->return_json(-2000,'','主题下载失败,请检查目录权限!');
}
finally{
unlink($file_name);
}
}
3 years ago
/**
3 years ago
* 验证订阅是否存在
*/
public function is_subscribe() {
//获取订阅SESSION状态
session_start();
//获取session订阅状态
$is_subscribe = $_SESSION['subscribe'];
//如果订阅是空的,则请求接口获取订阅状态
if ( !isset($is_subscribe) ) {
//获取当前站点信息
$subscribe = $this->db->get('on_options','value',[ 'key' => "s_subscribe" ]);
$domain = $_SERVER['HTTP_HOST'];
$subscribe = unserialize($subscribe);
//api请求地址
$api_url = API_URL."/v1/check_subscribe.php?order_id=".$subscribe['order_id']."&email=".$subscribe['email']."&domain=".$domain;
1 month ago
// echo $api_url;
3 years ago
try {
#GET HTTPS
$curl = curl_init($api_url);
#设置useragent
3 years ago
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36");
3 years ago
curl_setopt($curl, CURLOPT_FAILONERROR, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
#设置超时时间,最小为1s(可选)
curl_setopt($curl , CURLOPT_TIMEOUT, 30);
$html = curl_exec($curl);
curl_close($curl);
//解析json
$data = json_decode($html);
//var_dump($data->data->end_time);
//echo strtotime($data->data->end_time);
//var_dump($data->code);
//如果状态码返回200,并且订阅没有到期
if( (intval($data->code) === 200) && ( $data->data->end_time > ( strtotime( date("Y-m-d",time()) ) )) ) {
$_SESSION['subscribe'] = TRUE;
return TRUE;
}
else if( intval($data->code === -1000 ) ) {
$_SESSION['subscribe'] = FALSE;
return FALSE;
}
else{
$_SESSION['subscribe'] = NULL;
}
} catch (\Throwable $th) {
$_SESSION['subscribe'] = NULL;
}
}
if( $is_subscribe == TRUE ) {
return TRUE;
}
else{
return FALSE;
}
}
2 years ago
/**
* name:验证订阅,订阅不存在,则阻止
*/
public function check_is_subscribe(){
$result = $this->is_subscribe();
if( $result === FALSE ) {
$this->return_json(-2000,'','该功能需要订阅后才能使用!');
}
else if( $result === TRUE ) {
return TRUE;
}
else{
$this->return_json(-2000,'','该功能需要订阅后才能使用!');
}
}
3 years ago
/**
3 years ago
* 无脑下载更新程序
*/
public function down_updater() {
$url = API_URL."/update.tar.gz";
// echo $url;
// exit;
try {
//检查本地是否存在更新程序
$curl = curl_init($url);
#设置useragent
3 years ago
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36");
3 years ago
curl_setopt($curl, CURLOPT_FAILONERROR, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
#设置超时时间,最小为1s(可选)
curl_setopt($curl , CURLOPT_TIMEOUT, 60);
$html = curl_exec($curl);
curl_close($curl);
//var_dump($html);
//return $html;
//写入文件
file_put_contents("update.tar.gz",$html);
//解压覆盖文件
//解压文件
$phar = new PharData('update.tar.gz');
//路径 要解压的文件 是否覆盖
$phar->extractTo('./', null, true);
return TRUE;
} catch (\Throwable $th) {
$this->return_json(-2000,"","更新程序下载失败!");
}
finally{
//再次判断更新程序是否存在
if( is_file("update.php") ) {
//判断是否大约0
$file_size = filesize("update.php");
if( $file_size < 100 ) {
$this->return_json(-2000,"","更新程序异常,请检查目录权限!");
}
else{
return TRUE;
}
}
else{
$this->return_json(-2000,"","更新程序下载失败,请检查目录权限!");
}
}
}
/**更新升级程序 */
public function up_updater() {
//如果不存在,则下载更新程序
if( !is_file("update.php") ) {
if ( $this->down_updater() ) {
$this->return_json(200,"","更新程序准备就绪!");
}
}
//如果存在更新程序,验证大小,大小不匹配时进行更新
if( is_file("update.tar.gz") ) {
//获取header头
$header = get_headers(API_URL."/update.tar.gz",1);
$lentgh = $header['Content-Length'];
//获取文件大小
$file_size = filesize("update.tar.gz");
//如果本地文件大小和远程文件大小不一致,则下载更新
if ( $file_size != $lentgh ) {
3 years ago
if ( $this->down_updater() ) {
//更新完毕后提示
3 years ago
$this->return_json(200,"","更新程序更新完毕!");
3 years ago
}
else{
$this->return_json(-2000,"","更新程序下载失败,请检查目录权限!");
}
3 years ago
}
else {
3 years ago
$this->return_json(200,"","更新程序(压缩包)准备就绪!");
3 years ago
}
}
else if( is_file("update.php") ) {
3 years ago
$this->return_json(200,"","更新程序(PHP)准备就绪!");
3 years ago
}
3 years ago
else{
3 years ago
$this->return_json(200,"","更新程序(其它)准备就绪!");
3 years ago
}
3 years ago
}
/**
* 校验更新程序
*/
public function check_version($version) {
//获取当前版本信息
$current_version = explode("-",file_get_contents("version.txt"));
$current_version = str_replace("v","",$current_version[0]);
//获取用户传递的版本
//$version = $_REQUEST['version'];
if( $version == $current_version ) {
$this->return_json(200,"","success");
}
else{
$this->return_json(-2000,"","更新失败,版本校验不匹配,请检查目录权限!");
}
}
3 years ago
//curl get请求
protected function curl_get($url,$timeout = 10) {
2 years ago
$curl = curl_init($url);
#设置useragent
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36");
curl_setopt($curl, CURLOPT_FAILONERROR, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
#设置超时时间,最小为1s(可选)
curl_setopt($curl , CURLOPT_TIMEOUT, $timeout);
3 years ago
2 years ago
$html = curl_exec($curl);
curl_close($curl);
return $html;
}
/**
* name:数据库备份接口
*/
public function backup_db(){
//验证请求
$this->auth($token);
//验证订阅
$this->check_is_subscribe();
$backup_dir = 'data/backup/';
//判断目录是否存在,不存在则创建
if( !is_dir($backup_dir) ) {
try {
mkdir($backup_dir,0755);
} catch (\Throwable $th) {
$this->return_json(-2000,'','备份目录创建失败,请检查目录权限!');
}
}
//尝试拷贝数据库进行备份
try {
//获取当前版本信息
$current_version = explode("-",file_get_contents("version.txt"));
$current_version = str_replace("v","",$current_version[0]);
$db_name = 'onenav_'.date("YmdHi",time()).'_'.$current_version.'.db3';
$backup_db_path = $backup_dir.$db_name;
copy('data/onenav.db3',$backup_db_path);
$this->return_json(200,$db_name,'success');
} catch (\Throwable $th) {
$this->return_json(-2000,'','备份目录创建失败,请检查目录权限!');
}
}
/**
* name:数据库备份列表
*/
public function backup_db_list() {
//验证请求
$this->auth($token);
//验证订阅
$this->check_is_subscribe();
//备份目录
$backup_dir = 'data/backup/';
//遍历备份列表
$dbs = scandir($backup_dir);
2 years ago
$newdbs = $dbs;
2 years ago
//去除.和..
for ($i=0; $i < count($dbs); $i++) {
if( ($dbs[$i] == '.') || ($dbs[$i] == '..') ) {
2 years ago
unset($newdbs[$i]);
2 years ago
}
}
2 years ago
//将删除后的数组重新赋值
$dbs = $newdbs;
2 years ago
//获取备份列表个数
$num = count($dbs);
2 years ago
2 years ago
//排序处理,按时间从大到小排序
2 years ago
rsort($dbs,2);
2 years ago
//如果大于10个,则删减为10个
if( $num > 10 ) {
for ($i=$num; $i > 10; $i--) {
//物理删除数据库
2 years ago
unlink($backup_dir.$dbs[$i-1]);
2 years ago
//删除数组最后一个元素
array_pop($dbs);
}
$count = 10;
}
else{
$count = $num;
}
//声明一个空数组
$data = [];
//遍历数据库,获取时间,大小
foreach ($dbs as $key => $value) {
$arr['id'] = $key;
$arr['name'] = $value;
$arr['mtime'] = date("Y-m-d H:i:s",filemtime($backup_dir.$value));
$arr['size'] = (filesize($backup_dir.$value) / 1024).'KB';
$data[$key] = $arr;
}
$datas = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $data
];
exit(json_encode($datas));
}
/**
* name:删除单个数据库备份
* @param $name:数据库名称
*/
public function del_backup_db($name) {
//验证请求
$this->auth($token);
//验证订阅
$this->check_is_subscribe();
//使用正则表达式判断数据库名称是否合法
$pattern = '/^onenav_[0-9\-]+_[0-9.]+(db3)$/';
if( !preg_match_all($pattern,$name) ) {
$this->return_json(-2000,'','数据库名称不合法!');
}
//数据库目录
$backup_dir = 'data/backup/';
//删除数据库
try {
unlink($backup_dir.$name);
$this->return_json(200,'',"备份数据库已被删除!");
} catch (\Throwable $th) {
$this->return_json(-2000,'',"删除失败,请检查目录权限!");
}
}
/**
* name:恢复数据库备份
* @param $name:备份数据库名称
*/
public function restore_db($name) {
//验证请求
$this->auth($token);
//验证订阅
$this->check_is_subscribe();
//使用正则表达式判断数据库名称是否合法
$pattern = '/^onenav_[0-9\-]+_[0-9.]+(db3)$/';
if( !preg_match_all($pattern,$name) ) {
$this->return_json(-2000,'','数据库名称不合法!');
}
//数据库目录
$backup_dir = 'data/backup/';
//恢复数据库
try {
copy($backup_dir.$name,'data/onenav.db3');
$this->return_json(200,'','数据库已回滚为'.$name);
} catch (\Throwable $th) {
$this->return_json(-2000,'',"回滚失败,请检查目录权限!");
}
}
2 years ago
/**
* name:获取OneNav信息
*/
public function app_info($token) {
//验证请求
$this->auth($token);
//获取PHP版本
$data['php_version'] = PHP_VERSION;
//获取OneNav版本
$data['onenav_version'] = file_get_contents("version.txt");
//获取分类数量
$data['cat_num'] = $this->db->count("on_categorys");
//获取链接数量
$data['link_num'] = $this->db->count("on_links");
//获取用户名
$data['username'] = USER;
1 week ago
// 获取用户邮箱
$data['email'] = EMAIL;
2 years ago
//返回JSON数据
$this->return_json(200,$data,"success");
}
/**
* name:下载数据库
*/
public function down_db($name) {
//验证请求
$this->auth($token);
//使用正则表达式判断数据库名称是否合法
$pattern = '/^onenav_[0-9\-]+_[0-9.]+(db3)$/';
if( !preg_match_all($pattern,$name) ) {
$this->return_json(-2000,'','数据库名称不合法!');
}
//数据库目录
$backup_dir = 'data/backup/';
//拼接数据库路径
$full_path = $backup_dir.$name;
if( !file_exists($full_path) ) {
header('HTTP/1.1 404 NOT FOUND');
}
else{
// 以只读和二进制模式打开文件
$file = fopen($full_path, "rb");
// 告诉浏览器这是一个文件流格式的文件
Header("Content-type: application/octet-stream");
// 请求范围的度量单位
Header("Accept-Ranges: bytes");
// Content-Length是指定包含于请求或响应中数据的字节长度
Header("Accept-Length: " . filesize($full_path));
// 用来告诉浏览器,文件是可以当做附件被下载,下载后的文件名称为$file_name该变量的值。
Header("Content-Disposition: attachment; filename=" . $name);
// 读取文件内容并直接输出到浏览器
echo fread($file, filesize($full_path));
fclose($file);
exit();
}
}
2 years ago
/**
* name:创建分享
*/
public function create_share($data) {
//验证请求
$this->auth($token);
//如果订阅不存在
if ( $this->is_subscribe() === FALSE ) {
$this->return_json(-2000,'','此功能需要订阅后才能使用!');
}
//设置默认数据
//随机8位分享ID
$data['sid'] = GetRandStr(8);
/**
* 判断到期时间
*/
//获取当前时间
$c_time = strtotime( $data['add_time'] );
$e_time = strtotime( $data['expire_time'] );
if( $c_time > $e_time ) {
$this->return_json(-2000,'','到期日期不能小于当前日期!');
}
/**
* 判断密码
*/
if( strlen($data['password']) > 16 ) {
$this->return_json(-2000,'','密码长度不能超过16位!');
}
$pattern = "/[A-Za-z0-9]{4,16}/";
//var_dump(preg_match($pattern,$data['password']));
if( !empty($data['password']) && !preg_match($pattern,$data['password']) ) {
$this->return_json(-2000,'','密码只能由4-16位字母和数字组成!');
}
//插入数据库
$result = $this->db->insert("on_shares",$data);
if( $result ) {
$this->return_json(200,'','success');
}
else{
$this->return_json(-2000,'','写入数据库失败!');
}
}
/**
* 分享列表
*/
public function share_list($data){
//验证请求
$this->auth($token);
$limit = $data['limit'];
$offset = ($data['page'] - 1) * $data['limit'];
//$fid = @$data['category_id'];
$count = $this->db->count('on_shares','*');
$sql = "SELECT *,(SELECT name FROM on_categorys WHERE id = on_shares.cid) AS category_name FROM on_shares ORDER BY id DESC LIMIT {$limit} OFFSET {$offset}";
//原生查询
$datas = $this->db->query($sql)->fetchAll();
$datas = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $datas
];
exit(json_encode($datas));
}
/**
* name:根据分享的SID查询指定分类下的所有链接
*/
public function get_sid_links($data) {
//获得SID
$sid = $data['sid'];
//获得密码
$password = $data['password'];
//判断SID是否合法
$pattern = "/[A-Za-z0-9]{8}$/";
if( (strlen($sid) !== 8) || !preg_match($pattern,$sid) ) {
$this->return_json(-2000,'','SID不合法!');
}
//根据SID查询得到分类ID
$share_data = $this->db->get("on_shares","*",[
"sid" => $sid
]);
//如果没有查询到数据
if( empty($share_data) ) {
$this->return_json(-2000,'','SID不存在!');
}
$cid = $share_data['cid'];
//查询分类名称
$category_info = $this->db->get("on_categorys",["name"],[
"id" => $cid
]);
$category_name = $category_info["name"];
//如果链接已经过期
$c_time = strtotime( date("Y-m-d H:i:s",time()) );
if ( $c_time > strtotime($share_data['expire_time']) ) {
$this->return_json(-2000,'','链接已过期!');
}
//如果分享密码不为空,则验证密码
if ( !empty($share_data['password']) && ( $share_data['password'] == $password) ) {
//根据分类ID(cid)查询该分类下的所有链接
$results = $this->db->select("on_links","*",[
"fid" => $cid,
"ORDER" => ["weight" => "DESC","id" => "DESC"]
]);
$data = [
"category_name" => $category_name,
"expire_time" => $share_data["expire_time"],
"results" => $results
];
$this->return_json(200,$data,'success');
}
else if ( empty($share_data['password']) ) {
//根据分类ID(cid)查询该分类下的所有链接
$results = $this->db->select("on_links","*",[
"fid" => $cid,
"ORDER" => ["weight" => "DESC","id" => "DESC"]
]);
$data = [
"category_name" => $category_name,
"expire_time" => $share_data["expire_time"],
"results" => $results
];
$this->return_json(200,$data,'success');
}
else{
$this->return_json(401,'','密码错误!');
}
}
/**
* name:删除分享
*/
public function del_share($data) {
//验证请求
$this->auth($token);
$id = $data['id'];
//如果id为空
if( empty($id) ){
$this->return_json(-2000,$results,'ID不能为空!');
}
$data = $this->db->delete('on_shares',[ 'id' => $id] );
if( $data ) {
$this->return_json(200,'','success');
}
else{
$this->return_json(-2000,'','删除失败!');
}
}
/**
* name:获取站点信息,不需要授权
*/
public function site_info() {
//获取当前站点信息
$site = $this->db->get('on_options','value',[ 'key' => "s_site" ]);
$site = unserialize($site);
$this->return_json(200,$site,'success');
}
2 years ago
/**
* name:删除链接图标
*/
public function del_link_icon(){
//验证授权
$this->auth($token);
//获取图标路径
$icon_path = trim($_POST['icon_path']);
//正则判断路径是否合法
$pattern = "/^data\/upload\/[0-9]+\/[0-9a-zA-Z]+\.(jpg|jpeg|png|bmp|gif|svg)$/";
//如果名称不合法,则终止执行
if( !preg_match($pattern,$icon_path) ){
$this->return_json(-2000,'','非法路径!');
}
//继续执行
//检查图标是否存在
if( !is_file($icon_path) ) {
$this->return_json(-2000,'','图标文件不存在,无需删除!');
}
//执行删除操作
if( unlink($icon_path) ) {
$this->return_json(200,'','success');
}
else{
$this->return_json(-2000,'','图标删除失败,请检查目录权限!');
}
}
2 years ago
/**
* name:优先使用POST获取数据,其次GET获取数据
*/
protected function getData($param) {
if(isset($_POST[$param])) {
return $_POST[$param];
} elseif(isset($_GET[$param])) {
return $_GET[$param];
} else {
return null;
}
}
/**
* name: 全局搜索
*/
public function global_search() {
//验证授权
$this->auth($token);
// 获取关键词
$keyword = htmlspecialchars( $this->getData("keyword") );
// 判断关键词长度
if( strlen($keyword) < 2 ) {
$this->return_json(-2000,'','The length of the keyword is too short.');
}
else if( strlen($keyword) > 32 ) {
$this->return_json(-2000,'','The keyword length is too long');
}
$keyword = '%'.$keyword.'%';
// 通过标题、链接、备用链接、描述进行模糊匹配
$data = $this->db->select('on_links', '*', [
"OR" => [
"title[~]" => $keyword,
"url[~]" => $keyword,
"url_standby[~]" => $keyword,
"description[~]" => $keyword,
],
"ORDER" => [
"weight" => "DESC"
]
]);
// 查询出分类名称
$categorys = $this->db->select("on_categorys",[
'id',
'name'
]);
// 遍历分类,以id作为键名
foreach ($categorys as $category) {
$newCategorys[$category['id']] = $category['name'];
}
// 遍历查询的数据,然后添加父级分类名称
foreach ($data as $key => $value) {
$data[$key]['category_name'] = $newCategorys[$value['fid']];
}
// 返回数据
$datas = [
'code' => 0,
'msg' => '',
'count' => count($data),
'data' => $data
];
exit( json_encode($datas) );
}
1 month ago
/**
* 批量更新链接排序
*
*/
public function update_link_order(){
//验证授权
$this->auth($token);
// 获取分类ID
$cid = intval($this->getData("category_id"));
// 获取json对象数据
// 获取前端传递的原始 JSON 数据
$rawData = file_get_contents('php://input');
// 将 JSON 数据转换为 PHP 关联数组
$jsonData = json_decode($rawData, true);
// 检查解析是否成功
if (json_last_error() !== JSON_ERROR_NONE) {
$this->return_json(-2000,'','JSON解析失败!');
}
// 批量更新on_links表的weight字段
foreach ($jsonData as $key => $value) {
$this->db->update('on_links', [
'weight' => $value['weight']
], [
'fid' => $cid,
'id' => intval($value['id'])
]);
}
// 返回成功
$this->return_json(200,'','success');
}
/**
* name:获取订阅信息
*/
public function get_subscribe() {
//验证授权
$this->auth($token);
//获取当前站点信息
$subscribe = $this->db->get('on_options','value',[ 'key' => "s_subscribe" ]);
$subscribe = unserialize($subscribe);
$this->return_json(200,$subscribe,'success');
}
/**
* 验证订阅是否有效
*/
public function get_subscribe_status(){
// var_dump($_SERVER['HTTP_HOST']);
//验证授权
$this->auth($token);
// 获取订阅结果
$result = $this->is_subscribe();
if( $result === TRUE ) {
$this->return_json(200,'Active','success');
}
else{
$this->return_json(-2000,'','failure');
}
}
1 week ago
/**
* name: 批量检测链接
* description:check_status,0:未检测,1:正常,2:异常,3:未知(比如启用了cf)
*/
public function batch_check_links(){
set_time_limit(1200); // 设置执行最大时间为20分钟
// 验证授权
$this->auth($token);
// 验证订阅
$this->check_is_subscribe();
// 记录开始时间
$start_time = microtime(true);
// 获取所有链接
$links = $this->db->select('on_links', '*');
// 设置并发限制(最大30个并发)
$max_concurrent_requests = 30;
$multi_curl = curl_multi_init(); // 初始化curl_multi
$curl_handles = []; // 存储curl句柄
$link_count = count($links); // 获取链接数量
$completed_links = 0; // 已完成检测的链接数
// 设置curl超时时间为20分钟(1200秒)
$timeout = 10;
// 错误链接
$error_num = 0;
// 并发处理每个链接
foreach ($links as $link) {
// 创建一个curl句柄
$ch = curl_init();
// 设置curl选项
curl_setopt($ch, CURLOPT_URL, $link['url']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回内容而不是输出
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 设置请求超时时间
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 设置连接超时时间
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // 跟随重定向
// 设置UA
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36");
curl_setopt($ch, CURLOPT_HTTPGET, true); // 确保使用GET请求
curl_setopt($ch, CURLOPT_HEADER, true); // 获取响应头
// 将curl句柄加入到multi句柄中
curl_multi_add_handle($multi_curl, $ch);
// 存储每个链接的ID,方便回调时更新状态
$curl_handles[$link['id']] = $ch;
}
// 执行curl请求并监听
do {
$status = curl_multi_exec($multi_curl, $active); // 执行请求
if ($status > 0) {
// 如果发生错误,输出错误信息
// echo "Curl error: " . curl_multi_strerror($status);
}
// 等待活动的请求完成
if ($active) {
curl_multi_select($multi_curl, 1); // 阻塞直到有活动的请求
}
} while ($active);
// 处理每个请求的返回结果
foreach ($curl_handles as $id => $ch) {
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 获取HTTP状态码
$error = curl_error($ch); // 获取cURL错误信息
$header = curl_multi_getcontent($ch); // 获取完整的响应内容,包括头部
// 获取Server头
preg_match('/^Server:\s*(.*)$/mi', $header, $matches);
$server_header = isset($matches[1]) ? $matches[1] : ''; // 获取Server头的值
// 获取当前时间戳
$last_checked_time = date('Y-m-d H:i:s');
1 week ago
// 定义 Web 服务器列表
$web_servers = [
'cloudflare',
'waf',
// 可以根据需要添加更多的 Web 服务器类型
'AkamaiGHost',
'JDCloudStarshield'
];
// 假设 $error 和 $http_code 已经定义
// 假设 $server_header 已经从响应头中获取
1 week ago
if ($error || $http_code >= 400) {
1 week ago
// 默认状态为异常
$check_status = 2; // 异常
// 遍历 Web 服务器列表,检查是否包含任何已知的 Web 服务器标识
foreach ($web_servers as $server) {
if (stripos($server_header, $server) !== false) {
// 如果找到匹配的服务器,设置为未知状态
$check_status = 3; // 未知
break; // 一旦找到匹配的服务器,跳出循环
}
}
// 如果没有匹配到任何已知的 Web 服务器,则认为是异常
if ($check_status == 2) {
1 week ago
// 错误数量+1
$error_num++;
}
1 week ago
}
else if( $http_code === 0 ) {
// HTTP 状态码为 0,表示链接超时,或者SSL证书过期
$check_status = 2; // 异常
// 错误数量+1
$error_num++;
}
else {
// HTTP 状态码小于 400,表示正常
1 week ago
$check_status = 1; // 正常
}
// 更新数据库字段
$this->db->update('on_links', [
'check_status' => $check_status,
'last_checked_time' => $last_checked_time
], ['id' => $id]);
// 删除curl句柄,释放资源
curl_multi_remove_handle($multi_curl, $ch);
curl_close($ch); // 关闭curl句柄
// 完成一个链接的检测
$completed_links++;
}
// 关闭multi句柄
curl_multi_close($multi_curl);
// 记录结束时间
$end_time = microtime(true);
// 计算总共花费的时间
$elapsed_time = $end_time - $start_time;
// 精确到s就行
$elapsed_time = round($elapsed_time, 2);
// 返回成功
$this->return_json(200, [
'completed_links' => $completed_links,
'elapsed_time' => $elapsed_time,
'error_num' => $error_num
], 'success');
}
// 获取过渡页API
public function transition_page(){
//获取当前站点信息
$transition_page = $this->db->get('on_options','value',[ 'key' => "s_transition_page" ]);
$transition_page = unserialize($transition_page);
// 返回数据
$this->return_json(200,$transition_page,'success');
}
/**
1 week ago
* AI检索 (流式输出)
1 week ago
*/
public function ai_search() {
1 week ago
// 设置超时时间为120s
set_time_limit(120);
1 week ago
// 验证授权
$this->auth($token);
1 week ago
1 week ago
// 验证订阅
$this->check_is_subscribe();
1 week ago
// 从数据库获取API信息
$api = $this->get_options("ai_setting");
// 如果查询失败
if( !$api ) {
$this->return_json(-2000,'','获取参数失败!');
}
// 如果没有启用
if( $api->status === 'off' ) {
$this->return_json(-2000,'','AI功能未启用!');
}
// 查询到了结果
$url = $api->url;
$key = $api->sk;
$model = $api->model;
while (ob_get_level()) {
ob_end_flush();
}
ob_implicit_flush(true);
ini_set('zlib.output_compression', 'Off');
header('X-Accel-Buffering: no');
// 设置适当的响应头部
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
1 week ago
// 获取用户输入
1 week ago
$content = $_POST['content'];
$feature = empty($_POST['feature']) ? 'search' : $_POST['feature'];
1 week ago
// 查询出所有链接,只需要url, title, description, url_standby字段
1 week ago
$links = $this->db->select('on_links', ['url', 'title', 'description']);
if($feature == "search") {
// 设置温度
$temperature = 0.1;
$top_p = 0.7;
// 将链接数据转换为AI需要的JSON格式
$bookmarks = [];
foreach ($links as $link) {
$bookmarks[] = [
'title' => $link['title'],
'url' => $link['url'],
'description' => $link['description']
];
}
// 将数据转换为JSON格式
$bookmarks = json_encode($bookmarks);
// 创建AI请求的消息内容
$messages = [
[
"role" => "system",
"content" => "
用户会给你一段JSON格式的书签数据,其中包含每个链接的标题、URL和描述(描述可能为空)等信息。你需要根据用户提供的关键词,智能匹配与之相关的链接,并推荐3个额外的相关链接。请严格遵循以下规则:
### 规则:
1. **匹配链接**:
- **仅使用提供的书签数据进行匹配**,不得引用或使用任何其他外部信息。
- **对于没有描述或描述不充分的URL**,请基于你的知识推断该URL的含义,以便进行准确匹配。
- 返回的匹配链接必须与用户提供的关键词高度相关,优先匹配精确相关的内容。
- 根据相关性将匹配结果按从高到低排序,确保最相关的链接出现在列表前面。
- 匹配的结果不超过5个链接,如果无法匹配任何链接,请提醒用户。
2. **推荐额外链接**:
- **推荐的额外3个链接不来自用户提供的JSON数据**,应基于你所学的知识进行推荐。
- 确保这些推荐的链接与用户的需求相关,补充和扩展匹配结果。
- 不要推荐用户提供的JSON内容中的任何链接。
3. **输出格式**:
- **匹配结果**:以列表形式返回,应包含链接标题、URL和简短的链接描述,优先使用用户JSON中的标题和描述,如果没有或不完善你可以补充。
- **推荐结果**:以列表形式返回,应包含链接标题、URL和简短的链接描述。
"
],
[
"role" => "user",
"content" => "JSON书签列表为:" . $bookmarks // 你可以根据实际需求修改用户输入
],
[
"role" => "user",
"content" => $content // 你可以根据实际需求修改用户输入
]
];
}else{
// 设置温度
$temperature = 0.2;
$top_p = 1;
$messages = [
[
"role" => "system",
"content" => "请自动检测用户输入的语言。当输入内容是中文时,翻译成英文;当输入内容不是中文时,翻译成中文。只需返回翻译后的内容,不需要额外解释或描述。"
],
[
"role" => "user",
"content" => $content
]
1 week ago
];
}
1 week ago
// cURL 请求设置
1 week ago
$ch = curl_init();
1 week ago
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 不返回结果,进行流式输出
// curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $key,
"Content-Type: application/json"
]);
1 week ago
curl_setopt($ch, CURLOPT_POST, true);
1 week ago
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); // 例如,128字节
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"model" => $model,
"temperature" => $temperature,
"top_p" => $top_p,
"messages" => $messages,
"stream" => true
]));
// 设置输出流方式
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) {
// 实时输出流数据
echo $data;
flush(); // 强制刷新缓冲区,确保数据实时输出
return strlen($data); // 返回已写入的数据长度
// 查找数据中的 content 部分
// $pos = strpos($data, '"content":');
// // 如果 content 存在,提取其内容
// if ($pos !== false) {
// // 截取 content 字段内容
// $content_start = strpos($data, '"content":', $pos) + 10;
// $content_end = strpos($data, '"', $content_start + 1);
// // 提取 content 内容
// $content = substr($data, $content_start, $content_end - $content_start);
// // 去除 content 前后的双引号(如果存在)
// $content = trim($content, '"');
// // 输出符合 SSE 格式的数据:以 'data:' 开头
// echo "data: " . $content . "\n\n";
// flush(); // 强制刷新缓冲区,确保数据实时输出
// }
// // 返回原始数据的字节长度
// return strlen($data);
});
// 执行 cURL 请求
curl_exec($ch);
// 关闭 cURL 会话
1 week ago
curl_close($ch);
1 week ago
}
1 week ago
1 week ago
/**
* name:内部获取设置选项
*/
private function get_options($key) {
//验证授权
$this->auth($token);
//获取当前站点信息
$options = $this->db->get('on_options','value',[ 'key' => $key ]);
// 判断查询结果是否为空
if( empty($options) ) {
return false;
}
// 把选项转为对象
$options = json_decode($options);
return $options;
}
/**
* name:外部通用获取设置选项
*/
public function get_option_base(){
//验证授权
$this->auth($token);
// 获取key
$key = htmlspecialchars( trim( $_GET['key'] ));
$options = $this->db->get('on_options','value',[ 'key' => $key ]);
// 判断查询结果是否为空
if( empty($options) ) {
$this->return_json(-2000,'','获取参数失败!');
}
$result = json_decode($options);
// 获取成功
$this->return_json(200,$result,'success');
1 week ago
}
3 years ago
4 years ago
}
2 years ago
1 week ago