mirror of
https://github.com/675061370/xinyue-search.git
synced 2025-11-25 03:15:21 +08:00
feat: 新增多类型搜索源支持及后台管理功能
- 新增全网搜索线路配置功能,支持自定义搜索线路 - 新增多类型搜索源支持:API接口、TG频道、网页爬虫 - 新增后台搜索线路管理界面 - 优化搜索架构,由固定接口改为可配置模式 - 支持搜索线路权重设置,支持优先级调整
This commit is contained in:
@@ -18,6 +18,13 @@
|
||||
|
||||
## 🚀 更新日志
|
||||
|
||||
### v3.2
|
||||
- 🔄 新增 全网搜索线路配置功能,支持自定义搜索线路
|
||||
- 🌐 新增 多类型搜索源支持:API接口、TG频道、网页爬虫
|
||||
- ⚙️ 新增 后台搜索线路管理界面
|
||||
- 🛠 优化 搜索架构,由固定接口改为可配置模式
|
||||
- 📊 搜索线路权重设置,支持优先级调整
|
||||
|
||||
### v3.1
|
||||
- 🔍 优化 全网搜索模式:由直接展示转存结果,改为先显示第三方搜索结果,用户点击后再转存分享
|
||||
- 🌐 全网搜索支持 夸克网盘 和 百度网盘
|
||||
|
||||
258
app/admin/controller/ApiList.php
Normal file
258
app/admin/controller/ApiList.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use think\App;
|
||||
use app\admin\QfShop;
|
||||
use app\model\ApiList as ApiListModel;
|
||||
|
||||
class ApiList extends QfShop
|
||||
{
|
||||
public function __construct(App $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
//查询列表时允许的字段
|
||||
$this->selectList = "*";
|
||||
//查询详情时允许的字段
|
||||
$this->selectDetail = "*";
|
||||
//筛选字段
|
||||
$this->searchFilter = [
|
||||
];
|
||||
$this->insertFields = [
|
||||
//允许添加的字段列表
|
||||
"name","type","url","method","fixed_params","headers","field_map","status","weight","pantype","count","html_item","html_title","html_url","html_type","html_url2"
|
||||
];
|
||||
$this->updateFields = [
|
||||
//允许更新的字段列表
|
||||
"name","type","url","method","fixed_params","headers","field_map","status","weight","pantype","count","html_item","html_title","html_url","html_type","html_url2"
|
||||
];
|
||||
$this->insertRequire = [
|
||||
//添加时必须填写的字段
|
||||
// "字段名称"=>"该字段不能为空"
|
||||
"name"=>"分类名称必须填写",
|
||||
];
|
||||
$this->updateRequire = [
|
||||
//修改时必须填写的字段
|
||||
// "字段名称"=>"该字段不能为空"
|
||||
"name"=>"分类名称必须填写",
|
||||
];
|
||||
$this->model = new ApiListModel();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取列表接口
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
//校验Access与RBAC
|
||||
$error = $this->access();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
//查询数据
|
||||
$dataList = $this->model->order('weight', 'desc')->select();
|
||||
return jok('数据获取成功', $dataList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情基类 子类自动继承 如有特殊需求 可重写到子类 请勿修改父类方法
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
//校验Access与RBAC
|
||||
$error = $this->access();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
$id = input("id");
|
||||
if (!$id) {
|
||||
return jerr("ID参数必须填写", 400);
|
||||
}
|
||||
//根据主键获取一行数据
|
||||
$item = $this->model->where("id", $id)->field($this->selectDetail)->find();
|
||||
if (empty($item)) {
|
||||
return jerr("没有查询到数据", 404);
|
||||
}
|
||||
return jok('数据加载成功', $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加接口基类 子类自动继承 如有特殊需求 可重写到子类 请勿修改父类方法
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
//校验Access与RBAC
|
||||
$error = $this->access();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
//校验Insert字段是否填写
|
||||
$error = $this->validateInsertFields();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
//从请求中获取Insert数据
|
||||
$data = $this->getInsertDataFromRequest();
|
||||
//添加这行数据
|
||||
$data['create_time'] = time();
|
||||
$data['update_time'] = time();
|
||||
$this->model->insertGetId($data);
|
||||
return jok('添加成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改接口基类 子类自动继承 如有特殊需求 可重写到子类 请勿修改父类方法
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
//校验Access与RBAC
|
||||
$error = $this->access();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
$id = input("id");
|
||||
if (!$id) {
|
||||
return jerr("ID参数必须填写", 400);
|
||||
}
|
||||
|
||||
//根据主键获取一行数据
|
||||
$item = $this->model->where("id", $id)->field($this->selectDetail)->find();
|
||||
if (empty($item)) {
|
||||
return jerr("数据查询失败", 404);
|
||||
}
|
||||
|
||||
//校验Update字段是否填写
|
||||
$error = $this->validateUpdateFields();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
//从请求中获取Update数据
|
||||
$data = $this->getUpdateDataFromRequest();
|
||||
//根据主键更新这条数据
|
||||
$data['update_time'] = time();
|
||||
$this->model->where("id", $id)->update($data);
|
||||
return jok('修改成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除接口基类 子类自动继承 如有特殊需求 可重写到子类 请勿修改父类方法
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
//校验Access与RBAC
|
||||
$error = $this->access();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
$id = input("id");
|
||||
if (!$id) {
|
||||
return jerr("ID参数必须填写", 400);
|
||||
}
|
||||
|
||||
//根据主键获取一行数据
|
||||
$item = $this->model->where("id", $id)->field($this->selectDetail)->find();
|
||||
|
||||
if (empty($item)) {
|
||||
return jerr("数据查询失败", 404);
|
||||
}
|
||||
|
||||
//单个操作
|
||||
$map = ["id" => $id];
|
||||
$this->model->where($map)->delete();
|
||||
|
||||
return jok('删除成功');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 禁用接口基类 子类自动继承 如有特殊需求 可重写到子类 请勿修改父类方法
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
//校验Access与RBAC
|
||||
$error = $this->access();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
$id = input("id");
|
||||
if (!$id) {
|
||||
return jerr("ID参数必须填写", 400);
|
||||
}
|
||||
|
||||
|
||||
$d = [
|
||||
"status" => 0,
|
||||
"update_time" => time(),
|
||||
];
|
||||
|
||||
if (isInteger($id)) {
|
||||
//根据主键获取一行数据
|
||||
$item = $this->model->where("id", $id)->field($this->selectDetail)->find();
|
||||
if (empty($item)) {
|
||||
return jerr("数据查询失败", 404);
|
||||
}
|
||||
//单个操作
|
||||
$map = ["id" => $id];
|
||||
$this->model->where($map)->update($d);
|
||||
} else {
|
||||
//批量操作
|
||||
$list = explode(',', $id);
|
||||
$this->model->where("id", 'in', $list)->update($d);
|
||||
}
|
||||
return jok("禁用成功");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 启用接口基类 子类自动继承 如有特殊需求 可重写到子类 请勿修改父类方法
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
//校验Access与RBAC
|
||||
$error = $this->access();
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
$id = input("id");
|
||||
if (!$id) {
|
||||
return jerr("ID参数必须填写", 400);
|
||||
}
|
||||
|
||||
$d = [
|
||||
"status" => 1,
|
||||
"update_time" => time(),
|
||||
];
|
||||
|
||||
if (isInteger($id)) {
|
||||
//根据主键获取一行数据
|
||||
$item = $this->model->where("id", $id)->field($this->selectDetail)->find();
|
||||
if (empty($item)) {
|
||||
return jerr("数据查询失败", 404);
|
||||
}
|
||||
//单个操作
|
||||
$map = ["id" => $id];
|
||||
$this->model->where($map)->update($d);
|
||||
} else {
|
||||
//批量操作
|
||||
$list = explode(',', $id);
|
||||
$this->model->where("id", 'in', $list)->update($d);
|
||||
}
|
||||
return jok("启用成功");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use think\facade\Request;
|
||||
use app\api\QfShop;
|
||||
use app\model\Source as SourceModel;
|
||||
use app\model\Days as DaysModel;
|
||||
use app\model\ApiList as ApiListModel;
|
||||
|
||||
class Other extends QfShop
|
||||
{
|
||||
@@ -15,104 +16,804 @@ class Other extends QfShop
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->model = new SourceModel();
|
||||
$this->ApiListModel = new ApiListModel();
|
||||
}
|
||||
|
||||
public function search1()
|
||||
/**
|
||||
* 全网搜索 该接口用户网页端使用
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function web_search()
|
||||
{
|
||||
$searchdata = input('');
|
||||
if (empty($title)) {
|
||||
if (empty($searchdata['title'])) {
|
||||
return jerr("请输入名称");
|
||||
}
|
||||
$title = $searchdata['title'];
|
||||
}
|
||||
$apiType = $searchdata['num']??0;
|
||||
$is_type = $searchdata['is_type']??0;
|
||||
|
||||
|
||||
$searchList = []; // 查询的结果集
|
||||
$num_total = 10; // 最多想要几条结果
|
||||
$num_success = 0;
|
||||
|
||||
$sources = ['source1'];
|
||||
|
||||
// 遍历所有源
|
||||
foreach ($sources as $source) {
|
||||
if ($num_success >= $num_total) {
|
||||
break; // 有效结果数量已达到
|
||||
}
|
||||
|
||||
foreach ($source(Config('qfshop.is_quan_zc'),$title,$is_type,5,$apiType) as $value) {
|
||||
if ($num_success >= $num_total) {
|
||||
break; // 有效结果数量已达到
|
||||
}
|
||||
|
||||
// 如果 URL 不存在则新增 $value
|
||||
if (!$this->urlExists($searchList, $value['url'])) {
|
||||
$searchList[] = $value;
|
||||
$num_success++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 设置 SSE 响应头
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('Connection: keep-alive');
|
||||
header('X-Accel-Buffering: no'); // 防止 Nginx 缓冲
|
||||
|
||||
$searchList = array_map(function($value) {
|
||||
$value['is_type'] = determineIsType($value['url']);
|
||||
if(Config('qfshop.is_quan_type') != 1){
|
||||
$value['url'] = encryptObject($value['url']);
|
||||
}
|
||||
return $value;
|
||||
}, $searchList);
|
||||
$title = input('title', '');
|
||||
if (empty($title)) {
|
||||
echo "data: [DONE] 无搜索词\n\n";
|
||||
ob_flush();
|
||||
flush();
|
||||
exit;
|
||||
}
|
||||
$is_type = input('is_type', 0); //0夸克 2百度
|
||||
$is_show = input('is_show', 0); //0加密网址 1显示网址
|
||||
|
||||
// 查找一条可用线路
|
||||
$lines = $this->ApiListModel->where('status', 1)->where('pantype', $is_type)->order('weight desc')->select()->toArray();
|
||||
|
||||
return jok('网盘资源均来源于互联网,资源内容与本站无关', $searchList);
|
||||
// 获取自定义线路并合并到线路列表前面
|
||||
$lines = array_merge($this->getCustomLines(), $lines);
|
||||
|
||||
if (!$lines || count($lines) == 0) {
|
||||
echo "data: [DONE] 暂无可用线路\n\n";
|
||||
ob_flush();
|
||||
flush();
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$result = [];
|
||||
$type = $line['type'] ?? 'api';
|
||||
if ($type === 'tg') {
|
||||
$result = $this->handleTg($line, $title);
|
||||
} else if ($type === 'api') {
|
||||
$result = $this->handleApi($line, $title);
|
||||
} else if ($type === 'html') {
|
||||
$result = $this->handleWeb($line, $title);
|
||||
} else if ($type === 'kk') {
|
||||
$result = $this->handleKk($line, $title, $line['num']);
|
||||
}
|
||||
|
||||
foreach ($result as $item) {
|
||||
$item['is_type'] = determineIsType($item['url']);
|
||||
if(Config('qfshop.is_quan_zc')==1){
|
||||
//检测是否有效
|
||||
$infoData = $this->verificationUrl($item['url']);
|
||||
if (!empty($infoData['stoken'])) {
|
||||
$item['stoken'] = $infoData['stoken'];
|
||||
}
|
||||
if($infoData === 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(config('qfshop.is_quan_type') != 1 && $is_show != 1){
|
||||
$item['url'] = encryptObject($item['url']);
|
||||
}
|
||||
echo "data: " . str_replace(["\n", "\r"], '', json_encode($item, JSON_UNESCAPED_UNICODE)) . "\n\n";
|
||||
ob_flush();
|
||||
flush();
|
||||
}
|
||||
}
|
||||
echo "data: [DONE]\n\n";
|
||||
ob_flush();
|
||||
flush();
|
||||
exit;
|
||||
}
|
||||
|
||||
public function search2()
|
||||
|
||||
/**
|
||||
* 全网搜索 该接口仅用于机器人和微信对话时使用
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function all_search($param='')
|
||||
{
|
||||
$searchdata = input('');
|
||||
$title = $param ?: input('title', '');
|
||||
if (empty($title)) {
|
||||
if (empty($searchdata['title'])) {
|
||||
return jerr("请输入名称");
|
||||
}
|
||||
$title = $searchdata['title'];
|
||||
return jerr("请输入要看的内容");
|
||||
}
|
||||
$is_type = $searchdata['is_type']??0;
|
||||
$is_type = 0; //0夸克 2百度
|
||||
|
||||
$map[] = ['status', '=', 1];
|
||||
$map[] = ['is_delete', '=', 0];
|
||||
$map[] = ['is_time', '=', 1];
|
||||
$map[] = ['title|description', 'like', '%' . trim($title) . '%'];
|
||||
|
||||
$urls = $this->model->where($map)->field('source_id as id, title, url,is_time')->order('update_time', 'desc')->limit(5)->select()->toArray();
|
||||
if (!empty($urls)) {
|
||||
$ids = array_column($urls, 'id');
|
||||
$this->model->whereIn('source_id', $ids)->update(['update_time' => time()]);
|
||||
return !empty($param) ? $urls : jok('临时资源获取成功', $urls);
|
||||
}
|
||||
|
||||
//同一个搜索内容锁机
|
||||
if (Cache::has($title)) {
|
||||
// 检查缓存中是否已有结果
|
||||
return !empty($param) ? Cache::get($title) : jok('临时资源获取成功', Cache::get($title));
|
||||
}
|
||||
|
||||
// 检查是否有正在处理的请求
|
||||
if (Cache::has($title . '_processing')) {
|
||||
// 如果当前正在处理相同关键词的请求,等待结果
|
||||
$startTime = time(); // 记录开始时间
|
||||
while (Cache::has($title . '_processing')) {
|
||||
usleep(1000000); // 暂停1秒
|
||||
|
||||
// 检查是否超过60秒
|
||||
if (time() - $startTime > 60) {
|
||||
return !empty($param) ? [] : jok('临时资源获取成功', []);
|
||||
}
|
||||
}
|
||||
return !empty($param) ? Cache::get($title) : jok('临时资源获取成功', Cache::get($title));
|
||||
}
|
||||
|
||||
// 设置处理状态为正在处理
|
||||
Cache::set($title . '_processing', true, 60); // 锁定60秒
|
||||
|
||||
|
||||
$searchList = []; // 查询的结果集
|
||||
$num_total = 10; // 最多想要几条结果
|
||||
$$typeV = input('type', 0);
|
||||
|
||||
$searchList = []; //查询的结果集
|
||||
$datas = []; //最终转存后的数据
|
||||
$num_total = 2; //最多想要几条转存后的结果
|
||||
$num_success = 0;
|
||||
|
||||
$datas_zc = []; //最终未转存的数据
|
||||
$num_total_zc = $$typeV==1?3:0; //最多想要几条未转存的结果
|
||||
$num_success_zc = 0;
|
||||
|
||||
// 查找一条可用线路
|
||||
$lines = $this->ApiListModel->where('status', 1)->where('pantype', $is_type)->order('weight desc')->select()->toArray();;
|
||||
|
||||
// 定义源的顺序
|
||||
$sources = ['source2'];
|
||||
// 获取自定义线路并合并到线路列表前面
|
||||
$lines = array_merge($this->getCustomLines(), $lines);
|
||||
|
||||
if (!$lines || count($lines) == 0) {
|
||||
Cache::set($title, $datas, 60); // 缓存结果60秒
|
||||
Cache::delete($title . '_processing'); // 解锁
|
||||
return !empty($param) ? $datas : jok('临时资源获取成功', $datas);
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if ($num_success >= $num_total && $num_success_zc >= $num_total_zc) {
|
||||
break;
|
||||
}
|
||||
$result = [];
|
||||
$type = $line['type'] ?? 'api';
|
||||
if ($type === 'tg') {
|
||||
$result = $this->handleTg($line, $title);
|
||||
} else if ($type === 'api') {
|
||||
$result = $this->handleApi($line, $title);
|
||||
} else if ($type === 'html') {
|
||||
$result = $this->handleWeb($line, $title);
|
||||
} else if ($type === 'kk') {
|
||||
$result = $this->handleKk($line, $title, $line['num']);
|
||||
}
|
||||
|
||||
foreach ($result as $item) {
|
||||
if ($num_success < $num_total) {
|
||||
//检测是否有效
|
||||
$infoData = $this->verificationUrl($item['url']);
|
||||
if (!empty($infoData['stoken'])) {
|
||||
$item['stoken'] = $infoData['stoken'];
|
||||
}
|
||||
if ($infoData !== 0) {
|
||||
if (!$this->urlExists($searchList, $item['url'])) {
|
||||
$searchList[] = $item;
|
||||
$this->processUrl($item, $num_success, $datas);
|
||||
}
|
||||
}
|
||||
}else if($num_success_zc < $num_total_zc){
|
||||
//检测是否有效
|
||||
$infoData = $this->verificationUrl($item['url']);
|
||||
if (!empty($infoData['stoken'])) {
|
||||
$item['stoken'] = $infoData['stoken'];
|
||||
}
|
||||
if ($infoData !== 0) {
|
||||
if (!$this->urlExists($searchList, $item['url'])) {
|
||||
$titles = array_column($searchList, 'title');
|
||||
if (!in_array($item['title'], $titles)) {
|
||||
$searchList[] = $item;
|
||||
$datas_zc[] = $item;
|
||||
$num_success_zc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Cache::set($title, $datas, 60); // 缓存结果60秒
|
||||
Cache::delete($title . '_processing'); // 解锁
|
||||
|
||||
if($$typeV == 1){
|
||||
$datas = array_merge($datas, $datas_zc);
|
||||
}
|
||||
|
||||
// 遍历所有源
|
||||
foreach ($sources as $source) {
|
||||
if ($num_success >= $num_total) {
|
||||
break; // 有效结果数量已达到
|
||||
return !empty($param) ? $datas : jok('临时资源获取成功', $datas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自定义线路配置
|
||||
* @return array 自定义线路数组
|
||||
*/
|
||||
private function getCustomLines()
|
||||
{
|
||||
// 自定义线路 - 线路一
|
||||
// $customLines = array_map(function ($i) {
|
||||
// return [
|
||||
// 'name' => '自定义线路一',
|
||||
// 'pantype' => 0,
|
||||
// 'type' => 'kk',
|
||||
// 'count' => 5,
|
||||
// 'num' => $i,
|
||||
// ];
|
||||
// }, range(1, 6));
|
||||
|
||||
// 可以在这里添加更多自定义线路
|
||||
// 例如:
|
||||
/*
|
||||
$customLines[] = [
|
||||
'name' => '自定义线路二',
|
||||
'pantype' => 0,
|
||||
'type' => 'GG',
|
||||
'count' => 5,
|
||||
];
|
||||
*/
|
||||
return $customLines??[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口类型处理
|
||||
*/
|
||||
private function handleApi($line, $title)
|
||||
{
|
||||
$type = $line['pantype'];
|
||||
$maxCount = $line['count'];
|
||||
|
||||
// 根据类型选择搜索参数
|
||||
$panType = [
|
||||
0 => 'quark', // 夸克
|
||||
2 => 'baidu' // 百度
|
||||
];
|
||||
|
||||
if (!isset($panType[$type]) || $maxCount <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$url = $line['url'];
|
||||
$method = strtoupper($line['method']);
|
||||
$headers = json_decode($line['headers'], true) ?? [];
|
||||
$params = json_decode($line['fixed_params'], true) ?? [];
|
||||
|
||||
// 替换 {keyword}
|
||||
foreach ($params as &$val) {
|
||||
$val = str_replace('{keyword}', $title, $val);
|
||||
}
|
||||
|
||||
// headers 转为 curl 格式
|
||||
$headerArr = [];
|
||||
foreach ($headers as $k => $v) {
|
||||
$headerArr[] = "$k: $v";
|
||||
}
|
||||
|
||||
// 确保POST请求有正确的Content-Type
|
||||
if ($method === 'POST' && !isset($headers['Content-Type'])) {
|
||||
$headerArr[] = "Content-Type: application/x-www-form-urlencoded";
|
||||
}
|
||||
|
||||
// 简化参数处理
|
||||
$queryParams = $method === 'GET' ? $params : [];
|
||||
|
||||
// 处理POST数据
|
||||
if ($method === 'POST' && !empty($params)) {
|
||||
$postData = http_build_query($params);
|
||||
$result = curlHelper($url, $method, $postData, $headerArr, $queryParams);
|
||||
} else {
|
||||
$result = curlHelper($url, $method, $method === 'GET' ? null : $params, $headerArr, $queryParams);
|
||||
}
|
||||
|
||||
if (empty($result['body'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fieldMap = json_decode($line['field_map'], true);
|
||||
$response = json_decode($result['body'], true);
|
||||
$results = $this->extractList($response, $fieldMap, $type);
|
||||
|
||||
return array_slice($results, 0, $maxCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取字段
|
||||
*/
|
||||
protected function extractList($response, $fieldMap, $type)
|
||||
{
|
||||
$listPath = explode('.', $fieldMap['list_path'] ?? '');
|
||||
$listData = $response;
|
||||
foreach ($listPath as $key) {
|
||||
if (isset($listData[$key])) {
|
||||
$listData = $listData[$key];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
$fields = $fieldMap['fields'] ?? [];
|
||||
$result = [];
|
||||
foreach ($listData as $item) {
|
||||
$row = [];
|
||||
foreach ($fields as $targetKey => $sourcePath) {
|
||||
$value = $item;
|
||||
foreach (explode('.', $sourcePath) as $p) {
|
||||
$value = $value[$p] ?? null;
|
||||
}
|
||||
$row[$targetKey] = $value;
|
||||
|
||||
if($targetKey=='url'){
|
||||
// 将任何类型的值转换为字符串
|
||||
$stringValue = '';
|
||||
|
||||
if(is_array($value)) {
|
||||
// 原始数组转字符串
|
||||
$stringValue = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
// JSON 中链接中的 / 会变成 \/,替换回来
|
||||
$stringValue = str_replace('\/', '/', $stringValue);
|
||||
} else {
|
||||
$stringValue = (string)$value;
|
||||
}
|
||||
|
||||
// 从字符串中提取夸克网盘链接
|
||||
if($type === 0 && preg_match('/https:\/\/pan\.quark\.cn\/s\/[a-zA-Z0-9]+/', $stringValue, $urlMatch)) {
|
||||
$row['url'] = trim($urlMatch[0]);
|
||||
}
|
||||
// 从字符串中提取百度网盘链接
|
||||
else if($type === 2 && preg_match('/https:\/\/pan\.baidu\.com\/s\/[a-zA-Z0-9_-]+(\?pwd=[a-zA-Z0-9]+)?/', $stringValue, $urlMatch)) {
|
||||
$row['url'] = trim($urlMatch[0]);
|
||||
|
||||
// 检查URL中是否已有pwd参数,如果没有但字符串中有pwd字段,则添加
|
||||
if(!strpos($row['url'], '?pwd=') && preg_match('/["\'](pwd|code)["\']\s*:\s*["\']([^"\']+)["\']/', $stringValue, $pwdMatches)) {
|
||||
$row['url'] .= '?pwd=' . $pwdMatches[2];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$row['url'] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!empty($row['url'])){
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TG频道类型处理
|
||||
*/
|
||||
private function handleTg($line, $title)
|
||||
{
|
||||
$type = $line['pantype'];
|
||||
$maxCount = $line['count'];
|
||||
|
||||
// 根据类型选择搜索参数
|
||||
$panType = [
|
||||
0 => 'quark', // 夸克
|
||||
2 => 'baidu' // 百度
|
||||
];
|
||||
|
||||
if (!isset($panType[$type]) || $maxCount <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$url = 'https://t.me/s/NewQuark?q='.urlencode($title);
|
||||
$dom = getDom($url);
|
||||
$finder = new \DomXPath($dom);
|
||||
|
||||
$nodes = $finder->query('//div[contains(@class, "tgme_widget_message_text")]');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
// 获取 HTML 内容
|
||||
$htmlContent = $dom->saveHTML($node);
|
||||
|
||||
// 提取标题(名称:xxx)
|
||||
if (preg_match('/名称:(.+?)<br/i', $htmlContent, $titleMatch)) {
|
||||
$parsedItem['title'] = trim(strip_tags($titleMatch[1]));
|
||||
} else {
|
||||
$parsedItem['title'] = $title;
|
||||
}
|
||||
|
||||
foreach ($source(Config('qfshop.is_quan_zc'),$title,$is_type) as $value) {
|
||||
if ($num_success >= $num_total) {
|
||||
break; // 有效结果数量已达到
|
||||
}
|
||||
// 提取夸克链接(可支持百度扩展)
|
||||
$parsedItem['url'] = '';
|
||||
if ($type === 0 && preg_match('/https:\/\/pan\.quark\.cn\/s\/[a-zA-Z0-9]+/', $htmlContent, $urlMatch)) {
|
||||
$parsedItem['url'] = trim($urlMatch[0]);
|
||||
} elseif ($type === 2 && preg_match('/https:\/\/pan\.baidu\.com\/s\/[a-zA-Z0-9_-]+(\?pwd=[a-zA-Z0-9]+)?/', $htmlContent, $urlMatch)) {
|
||||
$parsedItem['url'] = trim($urlMatch[0]);
|
||||
}
|
||||
|
||||
// 如果 URL 不存在则新增 $value
|
||||
if (!$this->urlExists($searchList, $value['url'])) {
|
||||
$searchList[] = $value;
|
||||
$num_success++;
|
||||
// 过滤不合法或无效链接
|
||||
if ($parsedItem['title'] && $parsedItem['url']) {
|
||||
$results[] = $parsedItem;
|
||||
}
|
||||
|
||||
if (count($results) >= $maxCount) {
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 网页类型处理
|
||||
*/
|
||||
private function handleWeb($line, $title)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
// 替换搜索关键词并获取配置参数
|
||||
$url = str_replace('{keyword}', urlencode($title), $line['url']);
|
||||
|
||||
$parts = explode('+', $line['html_item'], 2);
|
||||
$tag = $parts[0] ?? '';
|
||||
$classString = $parts[1] ?? '';
|
||||
|
||||
$partsTitle = explode('+', $line['html_title'], 2);
|
||||
$tagTitle = $partsTitle[0] ?? '';
|
||||
$classStringTitle = $partsTitle[1] ?? '';
|
||||
|
||||
$partsUrl = explode('+', $line['html_url2'], 2);
|
||||
$tagUrl = $partsUrl[0] ?? '';
|
||||
$classStringUrl = $partsUrl[1] ?? '';
|
||||
|
||||
$maxCount = $line['count'] ?? 10;
|
||||
$type = $line['pantype'];
|
||||
|
||||
// 定义网盘链接匹配规则
|
||||
$panPatterns = [
|
||||
0 => '/https:\/\/pan\.quark\.cn\/s\/[a-zA-Z0-9]+/', // 夸克
|
||||
2 => '/https:\/\/pan\.baidu\.com\/s\/[a-zA-Z0-9_-]+(\?pwd=[a-zA-Z0-9]+)?/' // 百度(包含提取码)
|
||||
];
|
||||
|
||||
// 获取DOM并设置XPath查询
|
||||
$dom = getDom($url);
|
||||
if (!$dom) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
$finder = new \DomXPath($dom);
|
||||
$xpath = $this->buildXPathQuery($tag, $classString);
|
||||
$nodes = $finder->query($xpath);
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if (count($results) >= $maxCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
$html = $dom->saveHTML($node);
|
||||
$item = [
|
||||
'title' => '',
|
||||
'url' => '',
|
||||
];
|
||||
|
||||
// 提取资源标题
|
||||
$item['title'] = $this->extractTitle($html, $tagTitle, $classStringTitle);
|
||||
|
||||
// 尝试直接从当前HTML中提取网盘链接
|
||||
if (preg_match($panPatterns[$type], $html, $match)) {
|
||||
$item['url'] = trim($match[0]);
|
||||
} else {
|
||||
// 根据配置决定是否需要进入详情页
|
||||
if ($line['html_type'] == 1) {
|
||||
$item['url'] = $this->extractUrlFromDetailPage($html, $line, $url, $tagUrl, $classStringUrl, $panPatterns[$type]);
|
||||
} else {
|
||||
$item['url'] = $this->extractUrlFromListPage($html, $tagUrl, $classStringUrl, $panPatterns[$type]);
|
||||
}
|
||||
}
|
||||
|
||||
// 只添加同时有标题和URL的结果
|
||||
if ($item['title'] && $item['url']) {
|
||||
$results[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建XPath查询语句
|
||||
*
|
||||
* @param string $tag 标签名
|
||||
* @param string $classString 类名字符串
|
||||
* @return string XPath查询语句
|
||||
*/
|
||||
private function buildXPathQuery($tag, $classString)
|
||||
{
|
||||
$classArray = explode(' ', trim($classString));
|
||||
$xpathConditions = [];
|
||||
foreach ($classArray as $cls) {
|
||||
if (!empty($cls)) {
|
||||
$xpathConditions[] = "contains(concat(' ', normalize-space(@class), ' '), ' {$cls} ')";
|
||||
}
|
||||
}
|
||||
|
||||
return "//{$tag}" . (empty($xpathConditions) ? "" : "[" . implode(' and ', $xpathConditions) . "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从HTML中提取标题
|
||||
*
|
||||
* @param string $html HTML内容
|
||||
* @param string $tagTitle 标题标签
|
||||
* @param string $classStringTitle 标题类名
|
||||
* @return string 提取的标题
|
||||
*/
|
||||
private function extractTitle($html, $tagTitle, $classStringTitle)
|
||||
{
|
||||
// 尝试匹配"名称:xxx 描述:"格式
|
||||
if (preg_match('/名称:(.*?)\n\n描述:/s', $html, $match)) {
|
||||
return trim(strip_tags($match[1]));
|
||||
}
|
||||
|
||||
// 尝试根据标签和类名匹配
|
||||
$escapedClass = preg_quote($classStringTitle, '#');
|
||||
$escapedTag = preg_quote($tagTitle, '#');
|
||||
$pattern = '#<'.$escapedTag.'[^>]*class=["\'][^"\']*' . $escapedClass . '[^"\']*["\'][^>]*>(.*?)</'.$escapedTag.'>#s';
|
||||
|
||||
if (preg_match($pattern, $html, $titleMatch)) {
|
||||
return trim(strip_tags($titleMatch[1]));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从详情页提取URL
|
||||
*
|
||||
* @param string $html 列表页HTML
|
||||
* @param array $line 配置信息
|
||||
* @param string $baseUrl 基础URL
|
||||
* @param string $tagUrl URL标签
|
||||
* @param string $classStringUrl URL类名
|
||||
* @param string $panPattern 网盘链接匹配模式
|
||||
* @return string 提取的URL
|
||||
*/
|
||||
private function extractUrlFromDetailPage($html, $line, $baseUrl, $tagUrl, $classStringUrl, $panPattern)
|
||||
{
|
||||
list($tagD, $classStringD) = explode('+', $line['html_url'], 2);
|
||||
|
||||
// 构建匹配详情页链接的正则表达式
|
||||
$detailUrlPattern = $this->buildHrefPattern($tagD, $classStringD);
|
||||
|
||||
if (!preg_match($detailUrlPattern, $html, $match)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 处理相对URL
|
||||
$detailUrl = trim($match[1]);
|
||||
$fullDetailUrl = $this->buildFullUrl($detailUrl, $baseUrl);
|
||||
|
||||
// 获取详情页内容
|
||||
$dom2 = getDom($fullDetailUrl);
|
||||
if (!$dom2) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$finder2 = new \DomXPath($dom2);
|
||||
$xpath2 = $this->buildXPathQuery($tagUrl, $classStringUrl);
|
||||
$nodes2 = $finder2->query($xpath2);
|
||||
|
||||
// 遍历详情页节点查找网盘链接
|
||||
foreach ($nodes2 as $node2) {
|
||||
$html2 = $dom2->saveHTML($node2);
|
||||
|
||||
// 尝试从内容中提取
|
||||
$escapedClass = preg_quote($classStringUrl, '#');
|
||||
$escapedTag = preg_quote($tagUrl, '#');
|
||||
$contentPattern = '#<'.$escapedTag.'[^>]*class=["\'][^"\']*' . $escapedClass . '[^"\']*["\'][^>]*>(.*?)</'.$escapedTag.'>#s';
|
||||
|
||||
if (preg_match($contentPattern, $html2, $titleMatch)) {
|
||||
$extractedUrl = trim(strip_tags($titleMatch[1]));
|
||||
if (preg_match($panPattern, $extractedUrl, $urlMatch)) {
|
||||
return trim($urlMatch[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试从href属性中提取
|
||||
$hrefPattern = $this->buildHrefPattern($tagUrl, $classStringUrl);
|
||||
if (preg_match($hrefPattern, $html2, $match)) {
|
||||
$extractedUrl = trim($match[1]);
|
||||
if (preg_match($panPattern, $extractedUrl, $urlMatch)) {
|
||||
return trim($urlMatch[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表页直接提取URL
|
||||
*
|
||||
* @param string $html HTML内容
|
||||
* @param string $tagUrl URL标签
|
||||
* @param string $classStringUrl URL类名
|
||||
* @param string $panPattern 网盘链接匹配模式
|
||||
* @return string 提取的URL
|
||||
*/
|
||||
private function extractUrlFromListPage($html, $tagUrl, $classStringUrl, $panPattern)
|
||||
{
|
||||
// 尝试从内容中提取
|
||||
$escapedClass = preg_quote($classStringUrl, '#');
|
||||
$escapedTag = preg_quote($tagUrl, '#');
|
||||
$contentPattern = '#<'.$escapedTag.'[^>]*class=["\'][^"\']*' . $escapedClass . '[^"\']*["\'][^>]*>(.*?)</'.$escapedTag.'>#s';
|
||||
|
||||
if (preg_match($contentPattern, $html, $titleMatch)) {
|
||||
$extractedUrl = trim(strip_tags($titleMatch[1]));
|
||||
if (preg_match($panPattern, $extractedUrl, $urlMatch)) {
|
||||
return trim($urlMatch[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试从href属性中提取
|
||||
$hrefPattern = $this->buildHrefPattern($tagUrl, $classStringUrl);
|
||||
if (preg_match($hrefPattern, $html, $match)) {
|
||||
$extractedUrl = trim($match[1]);
|
||||
if (preg_match($panPattern, $extractedUrl, $urlMatch)) {
|
||||
return trim($urlMatch[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建href属性匹配模式
|
||||
*
|
||||
* @param string $tag 标签名
|
||||
* @param string $classString 类名
|
||||
* @return string 正则表达式
|
||||
*/
|
||||
private function buildHrefPattern($tag, $classString)
|
||||
{
|
||||
$escapedClass = preg_quote($classString, '#');
|
||||
$escapedTag = preg_quote($tag, '#');
|
||||
|
||||
if (empty($escapedClass)) {
|
||||
return '#<' . $escapedTag . '[^>]*href=["\']([^"\']+)["\']#i';
|
||||
} else {
|
||||
return '#<' . $escapedTag . '[^>]*class=["\'][^"\']*' . $escapedClass . '[^"\']*["\'][^>]*href=["\']([^"\']+)["\']#i';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整URL
|
||||
*
|
||||
* @param string $url 可能是相对URL
|
||||
* @param string $baseUrl 基础URL
|
||||
* @return string 完整URL
|
||||
*/
|
||||
private function buildFullUrl($url, $baseUrl)
|
||||
{
|
||||
if (strpos($url, 'http') !== 0) {
|
||||
$parsed = parse_url($baseUrl);
|
||||
$base = $parsed['scheme'] . '://' . $parsed['host'];
|
||||
return $base . $url;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义接口
|
||||
*/
|
||||
private function handleKk($line, $title, $apiType = 0)
|
||||
{
|
||||
$type = $line['pantype'];
|
||||
$maxCount = $line['count'];
|
||||
|
||||
$url2 = [];
|
||||
$urlDefault = "https://m.kkkba.com";
|
||||
|
||||
// 网盘链接匹配正则
|
||||
$pattern = [
|
||||
0 => '/https:\/\/pan\.quark\.cn\/[^\s]+/', // 夸克
|
||||
2 => '/https:\/\/pan\.baidu\.com\/[^\s]+/', // 百度
|
||||
];
|
||||
|
||||
if (!isset($pattern[$type])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$res = curlHelper($urlDefault."/v/api/getToken", "GET", null, [], "", "", 5)['body'] ?? null;
|
||||
if (!$res) return $url2;
|
||||
} catch (Exception $err) {
|
||||
return $url2;
|
||||
}
|
||||
|
||||
$res = json_decode($res, true);
|
||||
$token = $res['token'] ?? '';
|
||||
if(empty($token)){
|
||||
return $url2;
|
||||
}
|
||||
|
||||
// 所有接口列表
|
||||
$allApiList = [
|
||||
1 => "/v/api/getJuzi",
|
||||
2 => "/v/api/search",
|
||||
// 3 => "/v/api/getXiaoyu",
|
||||
// 4 => "/v/api/getDJ",
|
||||
// 5 => "/v/api/getKK"
|
||||
];
|
||||
|
||||
// 根据 apiType 确定要调用的接口列表
|
||||
if ($apiType == 0) {
|
||||
// 全部接口
|
||||
$apiList = array_values($allApiList);
|
||||
} elseif (isset($allApiList[$apiType])) {
|
||||
// 指定某个接口
|
||||
$apiList = [$allApiList[$apiType]];
|
||||
} else {
|
||||
// 错误类型,直接返回空
|
||||
return [];
|
||||
}
|
||||
|
||||
// 请求头
|
||||
$urlData = array(
|
||||
'name' => $title,
|
||||
'token' => $token
|
||||
);
|
||||
$headers = ['Content-Type: application/json'];
|
||||
|
||||
foreach ($apiList as $apiUrl) {
|
||||
try {
|
||||
$response = curlHelper($urlDefault.$apiUrl, "POST", json_encode($urlData), $headers, "", "", 5);
|
||||
$res = isset($response['body']) ? json_decode($response['body'], true) : null;
|
||||
} catch (Exception $err) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($res['list']) || !is_array($res['list'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($res['list'] as $value) {
|
||||
if (preg_match($pattern[$type], $value['answer'], $matches)) {
|
||||
$link = $matches[0];
|
||||
if (preg_match('/提取码[::]?\s*([a-zA-Z0-9]{4})/', $value['answer'], $codeMatch)) {
|
||||
$link .= '?pwd=' . $codeMatch[1];
|
||||
}
|
||||
$titleText = preg_replace('/\s*[\((]?(夸克|百度)?[\))]?\s*/u', '', $value['answer']??'');
|
||||
$url2[] = [
|
||||
'title' => $titleText,
|
||||
'url' => $link
|
||||
];
|
||||
if (count($url2) >= $maxCount) {
|
||||
return $url2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$searchList = array_map(function($value) {
|
||||
$value['is_type'] = determineIsType($value['url']);
|
||||
if(Config('qfshop.is_quan_type') != 1){
|
||||
$value['url'] = encryptObject($value['url']);
|
||||
}
|
||||
return $value;
|
||||
}, $searchList);
|
||||
return $url2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证夸克地址是否有效
|
||||
* @return array
|
||||
*/
|
||||
private function verificationUrl($url) {
|
||||
$code = '';
|
||||
if (preg_match('/\?pwd=([^,\s&]+)/', $url, $pwdMatch)) {
|
||||
$code = trim($pwdMatch[1]);
|
||||
}
|
||||
$urlData = [
|
||||
'url' => $url,
|
||||
'code' => $code,
|
||||
'isType' => 1
|
||||
];
|
||||
|
||||
$transfer = new \netdisk\Transfer();
|
||||
$res = $transfer->transfer($urlData);
|
||||
|
||||
if ($res['code'] !== 200) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return jok('网盘资源均来源于互联网,资源内容与本站无关', $searchList);
|
||||
return $res['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,86 +887,6 @@ class Other extends QfShop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全网搜索 该接口仅用于微信自动回复
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function all_search($param='')
|
||||
{
|
||||
$title = $param ?: input('post.title', '');
|
||||
if (empty($title)) {
|
||||
return jerr("请输入要看的内容");
|
||||
}
|
||||
|
||||
$map[] = ['status', '=', 1];
|
||||
$map[] = ['is_delete', '=', 0];
|
||||
$map[] = ['is_time', '=', 1];
|
||||
$map[] = ['title|description', 'like', '%' . trim($title) . '%'];
|
||||
|
||||
$urls = $this->model->where($map)->field('source_id as id, title, url,is_time')->order('update_time', 'desc')->limit(5)->select()->toArray();
|
||||
if (!empty($urls)) {
|
||||
$ids = array_column($urls, 'id');
|
||||
$this->model->whereIn('source_id', $ids)->update(['update_time' => time()]);
|
||||
return !empty($param) ? $urls : jok('临时资源获取成功', $urls);
|
||||
}
|
||||
|
||||
//同一个搜索内容锁机
|
||||
if (Cache::has($title)) {
|
||||
// 检查缓存中是否已有结果
|
||||
return !empty($param) ? Cache::get($title) : jok('临时资源获取成功', Cache::get($title));
|
||||
}
|
||||
|
||||
// 检查是否有正在处理的请求
|
||||
if (Cache::has($title . '_processing')) {
|
||||
// 如果当前正在处理相同关键词的请求,等待结果
|
||||
$startTime = time(); // 记录开始时间
|
||||
while (Cache::has($title . '_processing')) {
|
||||
usleep(1000000); // 暂停1秒
|
||||
|
||||
// 检查是否超过60秒
|
||||
if (time() - $startTime > 60) {
|
||||
return !empty($param) ? [] : jok('临时资源获取成功', []);
|
||||
}
|
||||
}
|
||||
return !empty($param) ? Cache::get($title) : jok('临时资源获取成功', Cache::get($title));
|
||||
}
|
||||
|
||||
|
||||
// 设置处理状态为正在处理
|
||||
Cache::set($title . '_processing', true, 60); // 锁定60秒
|
||||
|
||||
$searchList = []; //查询的结果集
|
||||
$datas = []; //最终数据
|
||||
$num_total = 2; //最多想要几条结果
|
||||
$num_success = 0;
|
||||
|
||||
// 定义源的顺序
|
||||
$sources = ['source1', 'source2'];
|
||||
|
||||
foreach ($sources as $source) {
|
||||
if ($num_success >= $num_total) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($source(true,$title) as $value) {
|
||||
if ($num_success >= $num_total) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$this->urlExists($searchList, $value['url'])) {
|
||||
$searchList[] = $value;
|
||||
$this->processUrl($value, $num_success, $datas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cache::set($title, $datas, 60); // 缓存结果60秒
|
||||
Cache::delete($title . '_processing'); // 解锁
|
||||
|
||||
return !empty($param) ? $datas : jok('临时资源获取成功', $datas);
|
||||
}
|
||||
|
||||
// 检查 URL 是否已存在(忽略查询参数)
|
||||
public function urlExists($searchList, $urlToCheck) {
|
||||
// 解析待检查的 URL
|
||||
|
||||
@@ -723,9 +723,14 @@ function getDom($url)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
// 设置超时时间
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 连接超时:5秒
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 响应超时:5秒
|
||||
// 临时跳过 SSL 验证(测试用)
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 避免跳转被拦
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0'); // 模拟浏览器UA
|
||||
$html = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
9
app/model/ApiList.php
Normal file
9
app/model/ApiList.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
use app\model\QfShop;
|
||||
|
||||
class ApiList extends QfShop
|
||||
{
|
||||
}
|
||||
@@ -278,6 +278,7 @@ INSERT INTO `qf_node` VALUES (112, '账号管理', '', 'qfadmin', 'source', 'dep
|
||||
INSERT INTO `qf_node` VALUES (113, '资源日志', '', 'qfadmin', 'source', 'log', 108, 8, 1, 'el-icon-discover', NULL, 0, 1712208103, 1726195583);
|
||||
INSERT INTO `qf_node` VALUES (114, '用户需求', '', 'qfadmin', 'source', 'feedback', 108, 1, 1, 'el-icon-edit', NULL, 0, 1712230638, 1712230717);
|
||||
INSERT INTO `qf_node` VALUES (118, '分类管理', '', 'qfadmin', 'source', 'category', 108, 9, 1, 'el-icon-s-operation', NULL, 0, 1716363477, 1726190129);
|
||||
INSERT INTO `qf_node` VALUES (119, '接口配置', '', 'qfadmin', 'source', 'apilist', '108', '1', '1', 'el-icon-link', NULL, '0', '1747119102', '1747119102');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for qf_source
|
||||
@@ -365,6 +366,30 @@ CREATE TABLE `qf_token` (
|
||||
PRIMARY KEY (`token_id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '授权信息表' ROW_FORMAT = Dynamic;
|
||||
|
||||
DROP TABLE IF EXISTS `qf_api_list`;
|
||||
CREATE TABLE `qf_api_list` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) NOT NULL COMMENT '线路名称',
|
||||
`type` varchar(20) NOT NULL DEFAULT 'api' COMMENT '接口类型:api(接口)、html(网页)、tg(TG频道)',
|
||||
`pantype` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 夸克 2百度',
|
||||
`url` varchar(255) DEFAULT NULL COMMENT '请求地址或入口URL',
|
||||
`method` varchar(10) DEFAULT 'GET' COMMENT '请求方式:GET/POST,仅用于api/html类型',
|
||||
`fixed_params` text COMMENT '固定请求参数(JSON格式)',
|
||||
`headers` text COMMENT '请求头信息(JSON格式)',
|
||||
`field_map` text COMMENT '返回字段映射(JSON格式)',
|
||||
`count` int(11) DEFAULT '0' COMMENT '最多取多少个资源',
|
||||
`html_item` varchar(255) DEFAULT NULL,
|
||||
`html_title` varchar(255) DEFAULT NULL,
|
||||
`html_url` varchar(255) DEFAULT NULL,
|
||||
`html_type` tinyint(4) DEFAULT '0',
|
||||
`html_url2` varchar(255) DEFAULT NULL,
|
||||
`weight` int(11) DEFAULT '0' COMMENT '权重,数值越大优先级越高',
|
||||
`status` tinyint(1) DEFAULT '1' COMMENT '是否启用:1启用,0禁用',
|
||||
`create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
|
||||
`update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='多线路接口配置表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for qf_user
|
||||
-- ----------------------------
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let cancelSource = null;
|
||||
let currentEventSource = null;
|
||||
function setType(type) {
|
||||
app.selectBtn()
|
||||
if(type == app.is_type) return
|
||||
@@ -252,47 +252,48 @@
|
||||
app.currentSource = source;
|
||||
if(app.currentSource == 1){
|
||||
if(app.QLoading || app.QList.length>0) return
|
||||
|
||||
if (cancelSource) {
|
||||
cancelSource.cancel('用户切换了数据源,取消上次请求');
|
||||
setTimeout(() => {
|
||||
app.QLoading = true
|
||||
}, 100);
|
||||
}
|
||||
cancelSource = axios.CancelToken.source();
|
||||
|
||||
app.QLoading = true
|
||||
|
||||
// search1线路 有5个接口
|
||||
const searchTypes = [1, 2, 3, 4, 5];
|
||||
const promises = [
|
||||
...searchTypes.map(num => searchRequest('search1', num)),
|
||||
searchRequest('search2')
|
||||
];
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
app.QList = []
|
||||
|
||||
// 创建新的 EventSource 连接前,确保关闭旧的连接
|
||||
if(currentEventSource) {
|
||||
currentEventSource.close();
|
||||
}
|
||||
|
||||
// 创建 EventSource 连接
|
||||
const params = new URLSearchParams({
|
||||
title: app.keyword,
|
||||
is_type: app.is_type
|
||||
})
|
||||
currentEventSource = new EventSource(`/api/other/web_search?${params.toString()}`)
|
||||
|
||||
// 监听消息
|
||||
currentEventSource.onmessage = function(event) {
|
||||
if(event.data.includes('[DONE]')) {
|
||||
currentEventSource.close()
|
||||
currentEventSource = null
|
||||
app.QLoading = false
|
||||
})
|
||||
.catch(error => {
|
||||
app.QLoading = false;
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
app.QList.push(data)
|
||||
} catch(e) {
|
||||
console.error('解析数据失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
currentEventSource.onerror = function(error) {
|
||||
console.error('SSE 连接错误:', error)
|
||||
currentEventSource.close()
|
||||
currentEventSource = null
|
||||
app.QLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
function searchRequest(api, num = null) {
|
||||
const params = {
|
||||
title: app.keyword,
|
||||
is_type: app.is_type,
|
||||
}
|
||||
if(num) params.num = num
|
||||
|
||||
return axios.post(`/api/other/${api}`, params, {
|
||||
cancelToken: cancelSource.token // 直接用全局变量
|
||||
})
|
||||
.then(res => {
|
||||
app.QList = app.QList.concat(res.data.data)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
497
public/views/qfadmin/source/apilist.html
Normal file
497
public/views/qfadmin/source/apilist.html
Normal file
@@ -0,0 +1,497 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>接口配置</title>
|
||||
{include file="common/header"/}
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<el-button icon="el-icon-plus" size="small" @click="clickAdd" plain>添加</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="60">
|
||||
</el-table-column>
|
||||
<el-table-column label="网盘类型" width="100" prop="name">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.pantype==2">百度网盘</span>
|
||||
<span v-else>夸克网盘</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="线路名称" prop="name"></el-table-column>
|
||||
<el-table-column label="地址" prop="url"></el-table-column>
|
||||
<el-table-column label="类型" width="100" prop="name">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.type=='html'">网页</span>
|
||||
<span v-else-if="scope.row.type=='tg'">TG频道</span>
|
||||
<span v-else>接口</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="count" label="获取资源数" width="150" align="center">
|
||||
</el-table-column>
|
||||
<el-table-column label="是否开启" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-switch v-model="scope.row.status==1?true:false"
|
||||
@change="clickStatus(scope.row)">
|
||||
</el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="weight" label="排序" width="100" align="center">
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
<template slot-scope="scope">
|
||||
<el-link type="primary" @click="clickEdit(scope.row)" :underline="false">编辑</el-link>
|
||||
<el-link type="danger" @click="clickDelete(scope.row)" :underline="false">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
|
||||
<!-- 添加框 -->
|
||||
<el-dialog title="添加线路" :visible.sync="dialogFormAdd" width="750px" :modal-append-to-body='false' append-to-body :close-on-click-modal='false'>
|
||||
<el-form :model="formAdd" :rules="rules" ref="formAdd">
|
||||
|
||||
<el-form-item prop="pantype" label="网盘类型" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formAdd.pantype">
|
||||
<el-radio :label="0">夸克网盘</el-radio>
|
||||
<el-radio :label="2">百度网盘</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="name" label="线路名称" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="count" label="总数限制" :label-width="formLabelWidth">
|
||||
<el-input-number v-model="formAdd.count" :min="0" :max="999" size="medium" style="width: 120px;"
|
||||
controls-position="right"></el-input-number>
|
||||
<span style="color: #999;">建议最大设置为5,即该接口最多只获取5条数据</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="type" label="类型" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formAdd.type">
|
||||
<el-radio label="api">API接口</el-radio>
|
||||
<el-radio label="tg">TG频道</el-radio>
|
||||
<el-radio label="html">网页爬虫</el-radio>
|
||||
</el-radio-group>
|
||||
<p style="color: red;" v-if="formAdd.type=='html'">不推荐!网页爬虫:配置复杂,请自行看描述;功能由AI编写,如无法获取也没法😄</p>
|
||||
<p style="color: red;" v-else-if="formAdd.type=='tg'">国内服务器无法使用;比如:https://t.me/s/NewQuark 就填写NewQuark即可</p>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="url" label="TG频道" :label-width="formLabelWidth" v-if="formAdd.type === 'tg'">
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.url"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="url" label="目标网址" :label-width="formLabelWidth" v-else-if="formAdd.type === 'html'">
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.url"></el-input>
|
||||
<span style="color: #999;">比如:https://www.baidu.com/s?wd={keyword},{keyword}是固定词请勿修改</span>
|
||||
</el-form-item>
|
||||
<el-form-item prop="url" label="接口地址" :label-width="formLabelWidth" v-else>
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.url"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<block v-if="formAdd.type === 'api'">
|
||||
<el-form-item prop="method" label="请求方式" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formAdd.method">
|
||||
<el-radio label="GET">GET</el-radio>
|
||||
<el-radio label="POST">POST</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="headers" label="请求头" :label-width="formLabelWidth">
|
||||
<el-input type="textarea" v-model="formAdd.headers" :rows="3" placeholder='{
|
||||
"User-Agent": "..."
|
||||
}' />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="fixed_params" label="接口参数" :label-width="formLabelWidth">
|
||||
<el-input type="textarea" v-model="formAdd.fixed_params" :rows="3" placeholder='{"search": "{keyword}"}'></el-input>
|
||||
<span style="color: #999;">将search改为实际的搜索字段即可,{keyword}请勿修改</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="field_map" label="字段映射" :label-width="formLabelWidth">
|
||||
<el-input type="textarea" v-model="formAdd.field_map" :rows="7" placeholder='{"title": "xxx", "url": "xxx"}'></el-input>
|
||||
<span style="color: #999;">结构参数名请勿修改,仅将中文修改为真实接口的字段名即可;数组字段一般是data或者data.list</span>
|
||||
</el-form-item>
|
||||
</block>
|
||||
|
||||
<block v-if="formAdd.type === 'html'">
|
||||
|
||||
<el-form-item prop="html_item" label="内容标签" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.html_item"></el-input>
|
||||
<span style="color: #999;line-height: 1.4;display: block;">
|
||||
请填写用于循环内容的 HTML 标签和 class,用于提取数据列表。格式:标签名+class名
|
||||
<br>
|
||||
示例:<br>
|
||||
div+item (选择所有 class="item" 的 div 标签)<br>
|
||||
article+post (选择所有 class="post" 的 article 标签)<br>
|
||||
li+result-item hot(选择所有 class 同时包含 "result-item" 和 "hot" 的 li 标签)
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_title" label="标题标签" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.html_title"></el-input>
|
||||
<span style="color: #999; line-height: 1.4; display: block;">
|
||||
标题标签是循环项中用于提取标题文字的元素;格式:标签名+class名。<br>
|
||||
例如:<br>
|
||||
h3+title 表示选择 `<h3 class="title">` 标签作为标题内容<br>
|
||||
a+link 表示 `<a class="link">` 标签中的文本作为标题<br>
|
||||
div+content-title 表示 `<div class="content-title">` 标签作为标题
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_type" label="详情页" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formAdd.html_type">
|
||||
<el-radio :label="0">不需要</el-radio>
|
||||
<el-radio :label="1">需要</el-radio>
|
||||
</el-radio-group>
|
||||
<p style="color: #999;">如果网盘链接不在列表页中,而是需要进入详情页后才能获取,请选择“需要”</p>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_url" label="详情页标签" :label-width="formLabelWidth" v-if="formAdd.html_type==1">
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.html_url"></el-input>
|
||||
<span style="color: #999; line-height: 1.4; display: block;">
|
||||
详情页标签是循环项中用于提取详情页网址的元素;格式:a+class名。只能a标签<br>
|
||||
例如:<br>
|
||||
a+post_url 表示获取元素 `<a class="post_url" href="https://..">` 上的href的值
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_url2" label="网盘链接" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formAdd.html_url2"></el-input>
|
||||
<span style="color: #999; line-height: 1.4; display: block;">
|
||||
网盘链接标签是循环项中用于提取网盘链接的元素;格式:标签名+class名。<br>
|
||||
将提取该元素第一个出现的网盘链接
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
</block>
|
||||
|
||||
<el-form-item prop="weight" label="排序" :label-width="formLabelWidth">
|
||||
<el-input-number v-model="formAdd.weight" :min="0" :max="999" size="medium" style="width: 120px;"
|
||||
controls-position="right" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="status" label="是否启用" :label-width="formLabelWidth">
|
||||
<el-switch v-model="formAdd.status" size="medium" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="postAdd()">确认添加</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<!-- 修改框 -->
|
||||
<el-dialog title="修改线路" :visible.sync="dialogFormEdit" width="750px" :modal-append-to-body='false' append-to-body :close-on-click-modal='false'>
|
||||
<el-form :model="formEdit" :rules="rules" ref="formEdit">
|
||||
<el-form-item prop="pantype" label="网盘类型" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formEdit.pantype">
|
||||
<el-radio :label="0">夸克网盘</el-radio>
|
||||
<el-radio :label="2">百度网盘</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="线路名称" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="count" label="总数限制" :label-width="formLabelWidth">
|
||||
<el-input-number v-model="formEdit.count" :min="0" :max="999" size="medium" style="width: 120px;"
|
||||
controls-position="right"></el-input-number>
|
||||
<span style="color: #999;">建议最大设置为5,即该接口最多只获取5条数据</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="type" label="类型" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formEdit.type">
|
||||
<el-radio label="api">API接口</el-radio>
|
||||
<el-radio label="tg">TG频道</el-radio>
|
||||
<el-radio label="html">网页爬虫</el-radio>
|
||||
</el-radio-group>
|
||||
<p style="color: red;" v-if="formEdit.type=='html'">不推荐!网页爬虫:配置复杂,请自行看描述;功能由AI编写,如无法获取也没法😄</p>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="url" label="TG频道" :label-width="formLabelWidth" v-if="formEdit.type === 'tg'">
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.url"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="url" label="目标网址" :label-width="formLabelWidth" v-else-if="formEdit.type === 'html'">
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.url"></el-input>
|
||||
<span style="color: #999;">比如:https://www.baidu.com/s?wd={keyword},{keyword}是固定词请勿修改</span>
|
||||
</el-form-item>
|
||||
<el-form-item prop="url" label="接口地址" :label-width="formLabelWidth" v-else>
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.url"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<block v-if="formEdit.type === 'api'">
|
||||
<el-form-item prop="method" label="请求方式" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formEdit.method">
|
||||
<el-radio label="GET">GET</el-radio>
|
||||
<el-radio label="POST">POST</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="headers" label="请求头" :label-width="formLabelWidth">
|
||||
<el-input type="textarea" v-model="formEdit.headers" :rows="3" placeholder='{
|
||||
"User-Agent": "..."
|
||||
}' />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="fixed_params" label="接口参数" :label-width="formLabelWidth">
|
||||
<el-input type="textarea" v-model="formEdit.fixed_params" :rows="3" placeholder='{"search": "{keyword}"}'></el-input>
|
||||
<span style="color: #999;">将search改为实际的搜索字段即可,{keyword}请勿修改</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="field_map" label="字段映射" :label-width="formLabelWidth">
|
||||
<el-input type="textarea" v-model="formEdit.field_map" :rows="7" placeholder='{"title": "xxx", "url": "xxx"}'></el-input>
|
||||
<span style="color: #999;">结构参数名请勿修改,仅将中文修改为真实接口的字段名即可;数组字段一般是data或者data.list</span>
|
||||
</el-form-item>
|
||||
</block>
|
||||
|
||||
<block v-if="formEdit.type === 'html'">
|
||||
|
||||
<el-form-item prop="html_item" label="内容标签" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.html_item"></el-input>
|
||||
<span style="color: #999;line-height: 1.4;display: block;">
|
||||
请填写用于循环内容的 HTML 标签和 class,用于提取数据列表。格式:标签名+class名
|
||||
<br>
|
||||
示例:<br>
|
||||
div+item (选择所有 class="item" 的 div 标签)<br>
|
||||
article+post (选择所有 class="post" 的 article 标签)<br>
|
||||
li+result-item hot(选择所有 class 同时包含 "result-item" 和 "hot" 的 li 标签)
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_title" label="标题标签" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.html_title"></el-input>
|
||||
<span style="color: #999; line-height: 1.4; display: block;">
|
||||
标题标签是循环项中用于提取标题文字的元素;格式:标签名+class名。<br>
|
||||
例如:<br>
|
||||
h3+title 表示选择 `<h3 class="title">` 标签作为标题内容<br>
|
||||
a+link 表示 `<a class="link">` 标签中的文本作为标题<br>
|
||||
div+content-title 表示 `<div class="content-title">` 标签作为标题
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_type" label="详情页" :label-width="formLabelWidth">
|
||||
<el-radio-group v-model="formEdit.html_type">
|
||||
<el-radio :label="0">不需要</el-radio>
|
||||
<el-radio :label="1">需要</el-radio>
|
||||
</el-radio-group>
|
||||
<p style="color: #999;">如果网盘链接不在列表页中,而是需要进入详情页后才能获取,请选择“需要”</p>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_url" label="详情页标签" :label-width="formLabelWidth" v-if="formEdit.html_type==1">
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.html_url"></el-input>
|
||||
<span style="color: #999; line-height: 1.4; display: block;">
|
||||
详情页标签是循环项中用于提取详情页网址的元素;格式:a+class名。只能a标签<br>
|
||||
例如:<br>
|
||||
a+post_url 表示获取元素 `<a class="post_url" href="https://..">` 上的href的值
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="html_url2" label="网盘链接" :label-width="formLabelWidth">
|
||||
<el-input size="medium" autocomplete="off" v-model="formEdit.html_url2"></el-input>
|
||||
<span style="color: #999; line-height: 1.4; display: block;">
|
||||
网盘链接标签是循环项中用于提取网盘链接的元素;格式:标签名+class名。<br>
|
||||
将提取该元素第一个出现的网盘链接
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
</block>
|
||||
|
||||
<el-form-item prop="weight" label="排序" :label-width="formLabelWidth">
|
||||
<el-input-number v-model="formEdit.weight" :min="0" :max="999" size="medium" style="width: 120px;"
|
||||
controls-position="right" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="status" label="是否启用" :label-width="formLabelWidth">
|
||||
<el-switch v-model="formEdit.status" size="medium" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="postEdit()">确认修改</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
{include file="common/footer"/}
|
||||
<script>
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
this.getList();
|
||||
return {
|
||||
formLabelWidth: '90px',
|
||||
dialogFormAdd: false,
|
||||
dialogFormEdit: false,
|
||||
loading: true,
|
||||
dataList: [],
|
||||
selectList: [],
|
||||
formAdd: {
|
||||
},
|
||||
formEdit: {
|
||||
},
|
||||
rules: {
|
||||
name: [ { required: true, message: '请输入分类名称', trigger: 'blur' }],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
postEdit() {
|
||||
var that = this;
|
||||
that.$refs["formEdit"].validate((valid) => {
|
||||
if (valid) {
|
||||
axios.post('/admin/api_list/update', Object.assign({}, PostBase, that.formEdit))
|
||||
.then(function (response) {
|
||||
that.getList();
|
||||
if (response.data.code == CODE_SUCCESS) {
|
||||
that.$message({
|
||||
message: response.data.message,
|
||||
type: 'success'
|
||||
});
|
||||
that.dialogFormEdit = false;
|
||||
} else {
|
||||
that.$message.error(response.data.message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
that.$message.error('服务器内部错误');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
postAdd() {
|
||||
var that = this;
|
||||
that.$refs['formAdd'].validate((valid) => {
|
||||
if (valid) {
|
||||
axios.post('/admin/api_list/add', Object.assign({}, PostBase, that.formAdd))
|
||||
.then(function (response) {
|
||||
that.getList();
|
||||
if (response.data.code == CODE_SUCCESS) {
|
||||
that.$message({
|
||||
message: response.data.message,
|
||||
type: 'success'
|
||||
});
|
||||
that.dialogFormAdd = false;
|
||||
} else {
|
||||
that.$message.error(response.data.message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
that.$message.error('服务器内部错误');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
clickAdd() {
|
||||
var that = this;
|
||||
that.formAdd = {
|
||||
pantype: 0,
|
||||
type: 'api',
|
||||
method: 'GET',
|
||||
weight: 0,
|
||||
count: 5,
|
||||
status: true,
|
||||
html_type: 0,
|
||||
fixed_params: `{
|
||||
"search": "{keyword}"
|
||||
}`,
|
||||
field_map: `{
|
||||
"list_path": "数组字段",
|
||||
"fields": {
|
||||
"title": "资源名称",
|
||||
"url": "资源地址"
|
||||
}
|
||||
}`
|
||||
};
|
||||
|
||||
that.dialogFormAdd = true;
|
||||
},
|
||||
clickDelete(row) {
|
||||
var that = this;
|
||||
this.$confirm('即将删除这个分类, 是否确认?', '删除提醒', {
|
||||
confirmButtonText: '删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
axios.post('/admin/api_list/delete', Object.assign({}, PostBase, {
|
||||
id: row.id
|
||||
}))
|
||||
.then(function (response) {
|
||||
that.getList();
|
||||
if (response.data.code == CODE_SUCCESS) {
|
||||
that.$message({
|
||||
message: response.data.message,
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
that.$message.error(response.data.message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
that.$message.error('服务器内部错误');
|
||||
});
|
||||
}).catch(() => {
|
||||
});
|
||||
},
|
||||
clickEdit(row) {
|
||||
var that = this;
|
||||
that.formEdit = row;
|
||||
axios.post('/admin/api_list/detail', Object.assign({}, PostBase, {
|
||||
id: row.id
|
||||
}))
|
||||
.then(function (response) {
|
||||
if (response.data.code == CODE_SUCCESS) {
|
||||
that.formEdit = response.data.data;
|
||||
that.formEdit.status = that.formEdit.status==1?true:false;
|
||||
that.dialogFormEdit = true;
|
||||
} else {
|
||||
that.$message.error(response.data.message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
that.$message.error('服务器内部错误');
|
||||
});
|
||||
|
||||
},
|
||||
getList() {
|
||||
var that = this;
|
||||
that.loading = true;
|
||||
axios.post('/admin/api_list/getList', Object.assign({}, PostBase))
|
||||
.then(function (response) {
|
||||
that.loading = false;
|
||||
if (response.data.code == CODE_SUCCESS) {
|
||||
that.dataList = response.data.data;
|
||||
} else {
|
||||
that.$message.error(response.data.message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
that.loading = false;
|
||||
that.$message.error('服务器内部错误');
|
||||
});
|
||||
},
|
||||
|
||||
clickStatus(row) {
|
||||
var that = this;
|
||||
let url = row.status ? '/admin/api_list/disable' : '/admin/api_list/enable'
|
||||
axios.post(url, Object.assign({}, PostBase, {
|
||||
id: row.id
|
||||
}))
|
||||
.then(function (response) {
|
||||
that.getList();
|
||||
if (response.data.code == CODE_SUCCESS) {
|
||||
that.$message({
|
||||
message: response.data.message,
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
that.$message.error(response.data.message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
that.$message.error('服务器内部错误');
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
</html>
|
||||
33
更新说明
Normal file
33
更新说明
Normal file
@@ -0,0 +1,33 @@
|
||||
3.1升级3.2 重装 或 更新文件和表 涉及更新文件如下
|
||||
|
||||
app/common.php
|
||||
app/api/controller/Other.php
|
||||
app/model/ApiList.php
|
||||
app/admin/controller/ApiList.php
|
||||
public/views/qfadmin/source/apilist.html
|
||||
public/views/index/news/list.html
|
||||
|
||||
CREATE TABLE qf_api_list (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
name varchar(100) NOT NULL COMMENT '线路名称',
|
||||
type varchar(20) NOT NULL DEFAULT 'api' COMMENT '接口类型:api(接口)、html(网页)、tg(TG频道)',
|
||||
pantype tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 夸克 2百度',
|
||||
url varchar(255) DEFAULT NULL COMMENT '请求地址或入口URL',
|
||||
method varchar(10) DEFAULT 'GET' COMMENT '请求方式:GET/POST,仅用于api/html类型',
|
||||
fixed_params text COMMENT '固定请求参数(JSON格式)',
|
||||
headers text COMMENT '请求头信息(JSON格式)',
|
||||
field_map text COMMENT '返回字段映射(JSON格式)',
|
||||
count int(11) DEFAULT '0' COMMENT '最多取多少个资源',
|
||||
html_item varchar(255) DEFAULT NULL,
|
||||
html_title varchar(255) DEFAULT NULL,
|
||||
html_url varchar(255) DEFAULT NULL,
|
||||
html_type tinyint(4) DEFAULT '0',
|
||||
html_url2 varchar(255) DEFAULT NULL,
|
||||
weight int(11) DEFAULT '0' COMMENT '权重,数值越大优先级越高',
|
||||
status tinyint(1) DEFAULT '1' COMMENT '是否启用:1启用,0禁用',
|
||||
create_time int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
|
||||
update_time int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='多线路接口配置表';
|
||||
|
||||
INSERT INTO `qf_node` VALUES (119, '接口配置', '', 'qfadmin', 'source', 'apilist', '108', '1', '1', 'el-icon-link', NULL, '0', '1747119102', '1747119102');
|
||||
Reference in New Issue
Block a user