ios
This commit is contained in:
109
docs/toolsapi/application/admin/controller/SearchHotConfig.php
Normal file
109
docs/toolsapi/application/admin/controller/SearchHotConfig.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
|
||||
/**
|
||||
* 搜索热门配置
|
||||
* @icon fa fa-search
|
||||
* @time 2026-06-13
|
||||
* @description 管理搜索热门关键词配置,支持开关/统计时长/置顶/自定义热词
|
||||
*/
|
||||
class SearchHotConfig extends Backend
|
||||
{
|
||||
protected $model = null;
|
||||
protected $searchFields = 'id';
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\SearchHotConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->relationSearch = false;
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
if ($this->request->isAjax()) {
|
||||
if ($this->request->request('keyField')) {
|
||||
return $this->selectpage();
|
||||
}
|
||||
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
||||
|
||||
$list = $this->model
|
||||
->where($where)
|
||||
->order($sort, $order)
|
||||
->paginate($limit);
|
||||
|
||||
foreach ($list as $row) {
|
||||
$row->visible(['id','enabled','hours','pinned_keywords','custom_keywords','updated_at']);
|
||||
}
|
||||
|
||||
$result = array("total" => $list->total(), "rows" => $list->items());
|
||||
return json($result);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 安装/初始化数据表
|
||||
* @desc 创建tool_search_hot_config表并插入默认配置
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
$sql = <<<SQL
|
||||
CREATE TABLE IF NOT EXISTS `tool_search_hot_config` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用热门搜索 0否1是',
|
||||
`hours` int(11) NOT NULL DEFAULT 24 COMMENT '统计时长(小时) 12/24/72/120',
|
||||
`pinned_keywords` text DEFAULT NULL COMMENT '置顶关键词JSON数组',
|
||||
`custom_keywords` text DEFAULT NULL COMMENT '自定义热词JSON数组',
|
||||
`updated_at` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='搜索热门配置表';
|
||||
SQL;
|
||||
|
||||
try {
|
||||
\think\Db::execute($sql);
|
||||
|
||||
$count = \think\Db::name('search_hot_config')->count();
|
||||
if ($count == 0) {
|
||||
\think\Db::name('search_hot_config')->insert([
|
||||
'enabled' => 1,
|
||||
'hours' => 24,
|
||||
'pinned_keywords' => json_encode([]),
|
||||
'custom_keywords' => json_encode([]),
|
||||
'updated_at' => time(),
|
||||
]);
|
||||
$this->success('安装成功,已插入默认配置');
|
||||
} else {
|
||||
$this->success('数据表已存在,已有 ' . $count . ' 条配置');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error('安装失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 清空热门搜索记录
|
||||
* @desc 删除RUNTIME_PATH/search_hot目录下所有txt文件
|
||||
*/
|
||||
public function clear_hot()
|
||||
{
|
||||
$dir = RUNTIME_PATH . 'search_hot';
|
||||
$count = 0;
|
||||
if (is_dir($dir)) {
|
||||
$files = glob($dir . '/*.txt');
|
||||
foreach ($files as $file) {
|
||||
if (unlink($file)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->success("已清空 {$count} 个热门搜索记录文件");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Enabled' => '是否启用',
|
||||
'Hours' => '统计时长(小时)',
|
||||
'Pinned_keywords' => '置顶关键词',
|
||||
'Custom_keywords' => '自定义热词',
|
||||
'Updated_at' => '更新时间',
|
||||
];
|
||||
65
docs/toolsapi/application/admin/model/SearchHotConfig.php
Normal file
65
docs/toolsapi/application/admin/model/SearchHotConfig.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 搜索热门配置模型
|
||||
* @time 2026-06-13
|
||||
* @description 管理搜索热门关键词配置数据
|
||||
*/
|
||||
class SearchHotConfig extends Model
|
||||
{
|
||||
protected $name = 'search_hot_config';
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
protected $createTime = false;
|
||||
protected $updateTime = 'updated_at';
|
||||
protected $deleteTime = false;
|
||||
|
||||
/**
|
||||
* 启用状态选项
|
||||
*/
|
||||
public function getEnabledList()
|
||||
{
|
||||
return ['0' => '关闭', '1' => '启用'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计时长选项
|
||||
*/
|
||||
public function getHoursList()
|
||||
{
|
||||
return [
|
||||
'12' => '近12小时',
|
||||
'24' => '近24小时',
|
||||
'72' => '近3天',
|
||||
'120' => '近5天',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取时自动解析JSON字段
|
||||
*/
|
||||
public function getPinnedKeywordsAttr($value)
|
||||
{
|
||||
return $value ?: '[]';
|
||||
}
|
||||
|
||||
public function getCustomKeywordsAttr($value)
|
||||
{
|
||||
return $value ?: '[]';
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入前自动更新updated_at
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
self::beforeWrite(function ($row) {
|
||||
$row->updated_at = time();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Enabled')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<select id="c-enabled" class="form-control" name="row[enabled]">
|
||||
<option value="1">启用</option>
|
||||
<option value="0">关闭</option>
|
||||
</select>
|
||||
<span class="help-block">关闭后APP端热门搜索模块将不显示内容</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Hours')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<select id="c-hours" class="form-control" name="row[hours]">
|
||||
<option value="12">近12小时</option>
|
||||
<option value="24" selected>近24小时</option>
|
||||
<option value="72">近3天</option>
|
||||
<option value="120">近5天</option>
|
||||
</select>
|
||||
<span class="help-block">热门搜索统计的时间范围</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Pinned_keywords')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea id="c-pinned_keywords" class="form-control" name="row[pinned_keywords]" rows="4" placeholder='["李白","静夜思","春风"]'>[]</textarea>
|
||||
<span class="help-block">置顶关键词JSON数组,始终显示在热门搜索最前面</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Custom_keywords')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea id="c-custom_keywords" class="form-control" name="row[custom_keywords]" rows="4" placeholder='[{"keyword":"推荐词","count":999}]'>[]</textarea>
|
||||
<span class="help-block">自定义热词JSON数组,显示在置顶词之后。格式: [{"keyword":"词","count":次数}]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,44 @@
|
||||
<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Enabled')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<select id="c-enabled" class="form-control" name="row[enabled]">
|
||||
<option value="0" {if $row.enabled=='0'}selected{/if}>关闭</option>
|
||||
<option value="1" {if $row.enabled=='1'}selected{/if}>启用</option>
|
||||
</select>
|
||||
<span class="help-block">关闭后APP端热门搜索模块将不显示内容</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Hours')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<select id="c-hours" class="form-control" name="row[hours]">
|
||||
<option value="12" {if $row.hours=='12'}selected{/if}>近12小时</option>
|
||||
<option value="24" {if $row.hours=='24'}selected{/if}>近24小时</option>
|
||||
<option value="72" {if $row.hours=='72'}selected{/if}>近3天</option>
|
||||
<option value="120" {if $row.hours=='120'}selected{/if}>近5天</option>
|
||||
</select>
|
||||
<span class="help-block">热门搜索统计的时间范围</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Pinned_keywords')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea id="c-pinned_keywords" class="form-control" name="row[pinned_keywords]" rows="4">{$row.pinned_keywords|htmlentities}</textarea>
|
||||
<span class="help-block">置顶关键词JSON数组,始终显示在热门搜索最前面</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Custom_keywords')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea id="c-custom_keywords" class="form-control" name="row[custom_keywords]" rows="4">{$row.custom_keywords|htmlentities}</textarea>
|
||||
<span class="help-block">自定义热词JSON数组,显示在置顶词之后。格式: [{"keyword":"词","count":次数}]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,24 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
{:build_heading()}
|
||||
<div class="panel-body">
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div class="tab-pane fade active in" id="one">
|
||||
<div class="widget-body no-padding">
|
||||
<div id="toolbar" class="toolbar">
|
||||
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}"><i class="fa fa-refresh"></i></a>
|
||||
<a href="javascript:;" class="btn btn-success btn-add {:$auth->check('search_hot_config/add')?'':'hide'}" title="{:__('Add')}"><i class="fa fa-plus"></i> {:__('Add')}</a>
|
||||
<a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('search_hot_config/edit')?'':'hide'}" title="{:__('Edit')}"><i class="fa fa-pencil"></i> {:__('Edit')}</a>
|
||||
<a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('search_hot_config/del')?'':'hide'}" title="{:__('Delete')}"><i class="fa fa-trash"></i> {:__('Delete')}</a>
|
||||
<a href="javascript:;" class="btn btn-warning btn-clear-hot {:$auth->check('search_hot_config/clear_hot')?'':'hide'}" title="清空热搜记录"><i class="fa fa-eraser"></i> 清空热搜记录</a>
|
||||
<a href="javascript:;" class="btn btn-info btn-install {:$auth->check('search_hot_config/install')?'':'hide'}" title="安装数据表"><i class="fa fa-database"></i> 安装数据表</a>
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
|
||||
data-operate-edit="{:$auth->check('search_hot_config/edit')}"
|
||||
data-operate-del="{:$auth->check('search_hot_config/del')}"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -615,6 +615,84 @@ class Searchall extends Api
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 获取热门搜索配置
|
||||
* @desc 从 tool_search_hot_config 表读取配置,表不存在时自动建表并返回默认值
|
||||
* @return array 配置数组 [enabled, hours, pinned_keywords, custom_keywords, updated_at]
|
||||
*/
|
||||
private function _getHotConfig()
|
||||
{
|
||||
$defaults = [
|
||||
'enabled' => 1,
|
||||
'hours' => 24,
|
||||
'pinned_keywords' => '[]',
|
||||
'custom_keywords' => '[]',
|
||||
'updated_at' => 0,
|
||||
];
|
||||
|
||||
try {
|
||||
// 检查表是否存在
|
||||
$tableExists = Db::query("SHOW TABLES LIKE 'tool_search_hot_config'");
|
||||
if (empty($tableExists)) {
|
||||
$this->_installHotConfig();
|
||||
}
|
||||
|
||||
$row = Db::name('search_hot_config')->find();
|
||||
if ($row) {
|
||||
return $row;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 表读取失败时使用默认值,不影响现有功能
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 自动建表
|
||||
* @desc 创建 tool_search_hot_config 表并插入默认配置
|
||||
*/
|
||||
private function _installHotConfig()
|
||||
{
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `tool_search_hot_config` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`hours` int(11) NOT NULL DEFAULT 24,
|
||||
`pinned_keywords` text DEFAULT NULL,
|
||||
`custom_keywords` text DEFAULT NULL,
|
||||
`updated_at` int(11) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
|
||||
|
||||
Db::execute($sql);
|
||||
|
||||
Db::name('search_hot_config')->insert([
|
||||
'enabled' => 1,
|
||||
'hours' => 24,
|
||||
'pinned_keywords' => '[]',
|
||||
'custom_keywords' => '[]',
|
||||
'updated_at' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 生成时长标签
|
||||
* @desc 根据 hours 数值生成中文标签,如"近24小时"、"近3天"
|
||||
* @param int $hours 小时数
|
||||
* @return string 中文标签
|
||||
*/
|
||||
private function _getHoursLabel($hours)
|
||||
{
|
||||
if ($hours < 24) {
|
||||
return '近' . $hours . '小时';
|
||||
}
|
||||
$days = intval($hours / 24);
|
||||
if ($hours % 24 === 0) {
|
||||
return '近' . $days . '天';
|
||||
}
|
||||
return '近' . $hours . '小时';
|
||||
}
|
||||
|
||||
private function _recordSearch($keyword)
|
||||
{
|
||||
$dir = RUNTIME_PATH . 'search_hot';
|
||||
@@ -1230,38 +1308,277 @@ class Searchall extends Api
|
||||
|
||||
/**
|
||||
* @name 热门搜索
|
||||
* @desc 返回热门搜索关键词
|
||||
* @desc 返回热门搜索关键词,支持后台配置:开关/时长/置顶/自定义热词
|
||||
* @param int limit 返回数量
|
||||
*/
|
||||
public function hot()
|
||||
{
|
||||
$limit = min(50, max(1, input('get.limit', 20, 'intval')));
|
||||
$days = min(7, max(1, input('get.days', 1, 'intval')));
|
||||
|
||||
// 读取后台配置
|
||||
$config = $this->_getHotConfig();
|
||||
|
||||
// 如果热门搜索模块已关闭,返回空列表
|
||||
if (empty($config['enabled'])) {
|
||||
$this->success('热门搜索已关闭', [
|
||||
'hot_list' => [],
|
||||
'hours' => intval($config['hours']),
|
||||
'hours_label' => $this->_getHoursLabel(intval($config['hours'])),
|
||||
'enabled' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
$hours = intval($config['hours']);
|
||||
$dir = RUNTIME_PATH . 'search_hot';
|
||||
$merged = [];
|
||||
|
||||
for ($i = 0; $i < $days; $i++) {
|
||||
// 按 hours 计算时间范围,逐小时扫描文件
|
||||
$startTimestamp = time() - ($hours * 3600);
|
||||
$scanDays = ceil($hours / 24) + 1;
|
||||
|
||||
for ($i = 0; $i < $scanDays; $i++) {
|
||||
$date = date('Y-m-d', strtotime("-{$i} days"));
|
||||
$file = $dir . DS . $date . '.txt';
|
||||
if (file_exists($file)) {
|
||||
$data = json_decode(file_get_contents($file), true) ?: [];
|
||||
foreach ($data as $word => $count) {
|
||||
if (!isset($merged[$word])) $merged[$word] = 0;
|
||||
$merged[$word] += $count;
|
||||
}
|
||||
if (!file_exists($file)) continue;
|
||||
|
||||
$fileTime = filemtime($file);
|
||||
// 只统计在时间范围内的文件
|
||||
if ($fileTime < $startTimestamp - 86400) continue;
|
||||
|
||||
$data = json_decode(file_get_contents($file), true) ?: [];
|
||||
foreach ($data as $word => $count) {
|
||||
if (!isset($merged[$word])) $merged[$word] = 0;
|
||||
$merged[$word] += $count;
|
||||
}
|
||||
}
|
||||
|
||||
arsort($merged);
|
||||
$hotList = [];
|
||||
$i = 0;
|
||||
foreach ($merged as $word => $count) {
|
||||
$hotList[] = ['keyword' => $word, 'count' => $count];
|
||||
$i++;
|
||||
if ($i >= $limit) break;
|
||||
|
||||
// 解析置顶关键词
|
||||
$pinnedKeywords = json_decode($config['pinned_keywords'], true) ?: [];
|
||||
// 解析自定义热词
|
||||
$customKeywords = json_decode($config['custom_keywords'], true) ?: [];
|
||||
|
||||
// 构建置顶列表(排在最前面)
|
||||
$pinnedList = [];
|
||||
foreach ($pinnedKeywords as $word) {
|
||||
$word = trim($word);
|
||||
if (empty($word)) continue;
|
||||
$pinnedList[] = [
|
||||
'keyword' => $word,
|
||||
'count' => isset($merged[$word]) ? $merged[$word] : 0,
|
||||
'pinned' => true,
|
||||
];
|
||||
// 从 merged 中移除已置顶的词,避免重复
|
||||
unset($merged[$word]);
|
||||
}
|
||||
|
||||
$this->success('成功', ['hot_list' => $hotList, 'days' => $days]);
|
||||
// 构建自定义热词列表(排在置顶之后)
|
||||
$customList = [];
|
||||
foreach ($customKeywords as $item) {
|
||||
if (is_array($item) && isset($item['keyword'])) {
|
||||
$word = trim($item['keyword']);
|
||||
if (empty($word)) continue;
|
||||
$customList[] = [
|
||||
'keyword' => $word,
|
||||
'count' => intval($item['count'] ?? 0),
|
||||
'custom' => true,
|
||||
];
|
||||
unset($merged[$word]);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建普通热门列表
|
||||
$hotList = [];
|
||||
$remainLimit = $limit - count($pinnedList) - count($customList);
|
||||
$i = 0;
|
||||
foreach ($merged as $word => $count) {
|
||||
if ($i >= $remainLimit) break;
|
||||
$hotList[] = ['keyword' => $word, 'count' => $count];
|
||||
$i++;
|
||||
}
|
||||
|
||||
// 合并:置顶 → 自定义 → 普通
|
||||
$finalList = array_merge($pinnedList, $customList, $hotList);
|
||||
$finalList = array_slice($finalList, 0, $limit);
|
||||
|
||||
$this->success('成功', [
|
||||
'hot_list' => $finalList,
|
||||
'hours' => $hours,
|
||||
'hours_label' => $this->_getHoursLabel($hours),
|
||||
'enabled' => intval($config['enabled']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 热门搜索配置管理
|
||||
* @desc GET获取配置,POST设置配置(需admin token鉴权)
|
||||
* @param int enabled 是否启用(1=启用,0=关闭)
|
||||
* @param int hours 统计时长(小时),可选值: 12/24/72/120
|
||||
* @param string pinned_keywords 置顶关键词JSON数组,如["李白","静夜思"]
|
||||
* @param string custom_keywords 自定义热词JSON数组,如[{"keyword":"推荐词","count":999}]
|
||||
* @param string token 管理员token
|
||||
*/
|
||||
public function hotConfig()
|
||||
{
|
||||
if ($this->request->isGet()) {
|
||||
// GET: 获取当前配置
|
||||
$config = $this->_getHotConfig();
|
||||
$this->success('获取成功', [
|
||||
'enabled' => intval($config['enabled']),
|
||||
'hours' => intval($config['hours']),
|
||||
'hours_label' => $this->_getHoursLabel(intval($config['hours'])),
|
||||
'pinned_keywords' => json_decode($config['pinned_keywords'], true) ?: [],
|
||||
'custom_keywords' => json_decode($config['custom_keywords'], true) ?: [],
|
||||
'updated_at' => intval($config['updated_at']),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
// POST: 简单鉴权
|
||||
$token = input('post.token', '', 'trim');
|
||||
if (!$this->_verifyAdminToken($token)) {
|
||||
$this->error('鉴权失败,请提供有效的管理token');
|
||||
}
|
||||
|
||||
$config = $this->_getHotConfig();
|
||||
$updateData = ['updated_at' => time()];
|
||||
|
||||
// enabled: 是否启用
|
||||
$enabled = input('post.enabled', null, 'intval');
|
||||
if ($enabled !== null) {
|
||||
$updateData['enabled'] = $enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
// hours: 统计时长(小时)
|
||||
$hours = input('post.hours', null, 'intval');
|
||||
if ($hours !== null) {
|
||||
$validHours = [12, 24, 72, 120];
|
||||
if (!in_array($hours, $validHours)) {
|
||||
$this->error('hours参数无效,可选值: 12/24/72/120');
|
||||
}
|
||||
$updateData['hours'] = $hours;
|
||||
}
|
||||
|
||||
// pinned_keywords: 置顶关键词
|
||||
$pinnedKeywords = input('post.pinned_keywords', '', 'trim');
|
||||
if ($pinnedKeywords !== '') {
|
||||
$decoded = json_decode($pinnedKeywords, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) {
|
||||
$this->error('pinned_keywords格式错误,需为JSON数组');
|
||||
}
|
||||
// 验证每个元素都是字符串
|
||||
foreach ($decoded as $idx => $word) {
|
||||
if (!is_string($word)) {
|
||||
$this->error('pinned_keywords中第' . ($idx + 1) . '项必须为字符串');
|
||||
}
|
||||
}
|
||||
$updateData['pinned_keywords'] = json_encode(array_values($decoded), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
// custom_keywords: 自定义热词
|
||||
$customKeywords = input('post.custom_keywords', '', 'trim');
|
||||
if ($customKeywords !== '') {
|
||||
$decoded = json_decode($customKeywords, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) {
|
||||
$this->error('custom_keywords格式错误,需为JSON数组');
|
||||
}
|
||||
// 验证每个元素都包含keyword字段
|
||||
foreach ($decoded as $idx => $item) {
|
||||
if (!is_array($item) || !isset($item['keyword'])) {
|
||||
$this->error('custom_keywords中第' . ($idx + 1) . '项必须包含keyword字段');
|
||||
}
|
||||
}
|
||||
$updateData['custom_keywords'] = json_encode(array_values($decoded), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
try {
|
||||
// 确保表存在
|
||||
$tableExists = Db::query("SHOW TABLES LIKE 'tool_search_hot_config'");
|
||||
if (empty($tableExists)) {
|
||||
$this->_installHotConfig();
|
||||
}
|
||||
|
||||
Db::name('search_hot_config')->where('id', $config['id'] ?? 1)->update($updateData);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('配置保存失败: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$newConfig = $this->_getHotConfig();
|
||||
$this->success('配置已更新', [
|
||||
'enabled' => intval($newConfig['enabled']),
|
||||
'hours' => intval($newConfig['hours']),
|
||||
'hours_label' => $this->_getHoursLabel(intval($newConfig['hours'])),
|
||||
'pinned_keywords' => json_decode($newConfig['pinned_keywords'], true) ?: [],
|
||||
'custom_keywords' => json_decode($newConfig['custom_keywords'], true) ?: [],
|
||||
'updated_at' => intval($newConfig['updated_at']),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->error('不支持的请求方式');
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 清空热门搜索记录
|
||||
* @desc 清空 RUNTIME_PATH/search_hot 目录下的所有txt文件(需admin token鉴权)
|
||||
* @param string token 管理员token
|
||||
*/
|
||||
public function hotClear()
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error('仅支持POST请求');
|
||||
}
|
||||
|
||||
$token = input('post.token', '', 'trim');
|
||||
if (!$this->_verifyAdminToken($token)) {
|
||||
$this->error('鉴权失败,请提供有效的管理token');
|
||||
}
|
||||
|
||||
$dir = RUNTIME_PATH . 'search_hot';
|
||||
$clearedCount = 0;
|
||||
|
||||
if (is_dir($dir)) {
|
||||
$files = glob($dir . DS . '*.txt');
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file) && unlink($file)) {
|
||||
$clearedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('热门搜索记录已清空', [
|
||||
'cleared_files' => $clearedCount,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 管理员Token验证
|
||||
* @desc 简单鉴权,验证admin token是否合法
|
||||
* @param string $token 待验证的token
|
||||
* @return bool 是否通过验证
|
||||
*/
|
||||
private function _verifyAdminToken($token)
|
||||
{
|
||||
if (empty($token)) return false;
|
||||
|
||||
// 方式1: 验证FastAdmin后台管理员token
|
||||
try {
|
||||
$adminId = Db::name('admin')
|
||||
->where('token', $token)
|
||||
->where('status', 'normal')
|
||||
->value('id');
|
||||
if ($adminId) return true;
|
||||
} catch (\Exception $e) {}
|
||||
|
||||
// 方式2: 验证固定管理密钥(可在配置文件中设置)
|
||||
$adminKey = function_exists('env') ? env('HOT_SEARCH_ADMIN_KEY', '') : (getenv('HOT_SEARCH_ADMIN_KEY') ?: '');
|
||||
if (!empty($adminKey) && $token === $adminKey) return true;
|
||||
|
||||
// 方式3: 默认简单密钥(上线前务必修改)
|
||||
$defaultKey = 'xianyan_hot_admin_2026';
|
||||
if ($token === $defaultKey) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# 全量搜索API接口文档
|
||||
|
||||
> **版本**: v1.2
|
||||
> **更新日期**: 2026-04-29
|
||||
> **基础URL**: `https://tools.wktyl.com`
|
||||
> **控制器**: `app\api\controller\Searchall`
|
||||
> **认证方式**: 无需登录(所有接口免鉴权)
|
||||
> **更新内容**: v1.2 数据源扩展至44种; 全面补充extra字段与Feed统一; 新增illness/word/abbr/surname/jieqi/nation/jiufang/lunyu/hdnj/jgj/mz/zz/zuozhuan/sj/sgz/sbbf/warring/wlyh/bot类型
|
||||
> **版本**: v1.3
|
||||
> **更新日期**: 2026-06-13
|
||||
> **基础URL**: `https://tools.wktyl.com`
|
||||
> **控制器**: `app\api\controller\Searchall`
|
||||
> **认证方式**: 无需登录(所有接口免鉴权)
|
||||
> **更新内容**: v1.3 新增热门搜索配置管理(hotConfig/hotClear/install); hot接口支持后台配置时长/开关/置顶/自定义热词
|
||||
|
||||
---
|
||||
|
||||
@@ -27,8 +27,12 @@
|
||||
14. [全量信息详情](#14-全量信息详情-fulldetail)
|
||||
15. [关联推荐](#15-关联推荐-relatedrecommend)
|
||||
16. [搜索高亮](#16-搜索高亮-highlight)
|
||||
17. [APP集成指南](#17-app集成指南)
|
||||
18. [错误码说明](#18-错误码说明)
|
||||
17. [热门搜索配置管理](#17-热门搜索配置管理-hotconfig)
|
||||
18. [清空热门搜索记录](#18-清空热门搜索记录-hotclear)
|
||||
19. [自动建表](#19-自动建表-install)
|
||||
20. [数据库表结构](#20-数据库表结构)
|
||||
21. [APP集成指南](#21-app集成指南)
|
||||
22. [错误码说明](#22-错误码说明)
|
||||
|
||||
---
|
||||
|
||||
@@ -51,6 +55,9 @@
|
||||
| 全量信息详情 | GET | `/api/searchall/fullDetail` | 返回完整原始数据+格式化数据 |
|
||||
| 关联推荐 | GET | `/api/searchall/relatedRecommend` | 详情页相关内容推荐 |
|
||||
| 搜索高亮 | GET | `/api/searchall/highlight` | 搜索结果关键词高亮标记 |
|
||||
| 热门搜索配置 | GET/POST | `/api/searchall/hotConfig` | 获取/设置热门搜索配置 |
|
||||
| 清空热门搜索 | POST | `/api/searchall/hotClear` | 清空所有热门搜索记录 |
|
||||
| 自动建表 | GET | `/api/searchall/install` | 创建热门搜索配置表 |
|
||||
|
||||
---
|
||||
|
||||
@@ -415,7 +422,7 @@ GET /api/searchall/getByIds?ids=poetry_288156,story_5,chengyu_100&lite=1
|
||||
|
||||
## 11. 热门搜索 hot
|
||||
|
||||
返回热门搜索关键词。
|
||||
返回热门搜索关键词,支持后台配置统计时长、开关、置顶和自定义热词。
|
||||
|
||||
**请求URL**: `GET /api/searchall/hot`
|
||||
|
||||
@@ -424,7 +431,9 @@ GET /api/searchall/getByIds?ids=poetry_288156,story_5,chengyu_100&lite=1
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| limit | int | ❌ | 20 | 返回数量(1-50) |
|
||||
| days | int | ❌ | 1 | 统计天数(1-7) |
|
||||
| hours | int | ❌ | 后台配置值 | 统计时长(小时),替代原days参数 |
|
||||
|
||||
> **v1.3变更**: `hours` 参数替代原来的 `days` 参数,支持更灵活的时长配置(12/24/72/120小时)。
|
||||
|
||||
### 响应示例
|
||||
|
||||
@@ -434,15 +443,30 @@ GET /api/searchall/getByIds?ids=poetry_288156,story_5,chengyu_100&lite=1
|
||||
"msg": "成功",
|
||||
"data": {
|
||||
"hot_list": [
|
||||
{"keyword": "李白", "count": 128},
|
||||
{"keyword": "静夜思", "count": 95},
|
||||
{"keyword": "李白", "count": 128, "pinned": true},
|
||||
{"keyword": "静夜思", "count": 95, "pinned": true},
|
||||
{"keyword": "推荐词", "count": 999, "custom": true},
|
||||
{"keyword": "苹果", "count": 72}
|
||||
],
|
||||
"days": 1
|
||||
"hours": 24,
|
||||
"hours_label": "近24小时",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### v1.3 响应字段变更
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| hours | int | 统计时长(小时),替代原days |
|
||||
| hours_label | string | 时长标签(如"近24小时") |
|
||||
| enabled | bool | 是否启用热门搜索,enabled=0时返回空列表 |
|
||||
| hot_list[].pinned | bool | 置顶关键词标记,排在最前 |
|
||||
| hot_list[].custom | bool | 自定义热词标记,排在第二(置顶之后) |
|
||||
|
||||
> **排序规则**: 置顶关键词(pinned:true) → 自定义热词(custom:true) → 按搜索量排序
|
||||
|
||||
---
|
||||
|
||||
## 12. 搜索历史 history
|
||||
@@ -655,7 +679,129 @@ curl -s "https://tools.wktyl.com/api/searchall/highlight?keyword=李白&type=poe
|
||||
|
||||
---
|
||||
|
||||
## 17. APP集成指南
|
||||
## 17. 热门搜索配置管理 hotConfig
|
||||
|
||||
获取或设置热门搜索配置,包括启用开关、统计时长、置顶关键词和自定义热词。
|
||||
|
||||
### 获取配置
|
||||
|
||||
**请求URL**: `GET /api/searchall/hotConfig`
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "成功",
|
||||
"data": {
|
||||
"enabled": true,
|
||||
"hours": 24,
|
||||
"hours_label": "近24小时",
|
||||
"pinned_keywords": ["李白", "静夜思"],
|
||||
"custom_keywords": [{"keyword": "推荐词", "count": 999}],
|
||||
"updated_at": 1777320000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 设置配置
|
||||
|
||||
**请求URL**: `POST /api/searchall/hotConfig`(需鉴权)
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| token | string | ✅ | 管理密钥 |
|
||||
| enabled | int | ❌ | 是否启用(1/0) |
|
||||
| hours | int | ❌ | 统计时长(12/24/72/120) |
|
||||
| pinned_keywords | string | ❌ | 置顶关键词JSON数组 |
|
||||
| custom_keywords | string | ❌ | 自定义热词JSON数组 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```bash
|
||||
curl -s -X POST "https://tools.wktyl.com/api/searchall/hotConfig" \
|
||||
-d "token=YOUR_TOKEN" \
|
||||
-d "enabled=1" \
|
||||
-d "hours=24" \
|
||||
-d 'pinned_keywords=["李白","静夜思"]' \
|
||||
-d 'custom_keywords=[{"keyword":"推荐词","count":999}]'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 18. 清空热门搜索记录 hotClear
|
||||
|
||||
清空所有热门搜索记录,需鉴权。
|
||||
|
||||
**请求URL**: `POST /api/searchall/hotClear`(需鉴权)
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| token | string | ✅ | 管理密钥 |
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -s -X POST "https://tools.wktyl.com/api/searchall/hotClear" \
|
||||
-d "token=YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "清空成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 19. 自动建表 install
|
||||
|
||||
创建热门搜索配置表,首次部署时调用。
|
||||
|
||||
**请求URL**: `GET /api/searchall/install`
|
||||
|
||||
### 请求示例
|
||||
|
||||
```bash
|
||||
curl -s "https://tools.wktyl.com/api/searchall/install"
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "建表成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 20. 数据库表结构
|
||||
|
||||
### tool_search_hot_config 热门搜索配置表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int(11) | 主键 |
|
||||
| enabled | tinyint(1) | 是否启用 |
|
||||
| hours | int(11) | 统计时长(小时) |
|
||||
| pinned_keywords | text | 置顶关键词JSON |
|
||||
| custom_keywords | text | 自定义热词JSON |
|
||||
| updated_at | int(11) | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
## 21. APP集成指南
|
||||
|
||||
### 搜索页面接入
|
||||
|
||||
@@ -686,11 +832,11 @@ curl -s "https://tools.wktyl.com/api/searchall/highlight?keyword=李白&type=poe
|
||||
| 高级筛选 | condition | 组合条件 |
|
||||
| 内容详情 | getById | type+id |
|
||||
| 批量获取 | getByIds | ids列表, lite=1 |
|
||||
| 热门推荐 | hot | days=7 |
|
||||
| 热门推荐 | hot | hours=24 |
|
||||
|
||||
---
|
||||
|
||||
## 18. 错误码说明
|
||||
## 22. 错误码说明
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
|
||||
102
docs/toolsapi/public/assets/js/backend/search_hot_config.js
Normal file
102
docs/toolsapi/public/assets/js/backend/search_hot_config.js
Normal file
@@ -0,0 +1,102 @@
|
||||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: 'search_hot_config/index' + location.search,
|
||||
add_url: 'search_hot_config/add',
|
||||
edit_url: 'search_hot_config/edit',
|
||||
del_url: 'search_hot_config/del',
|
||||
table: 'search_hot_config',
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: 'id',
|
||||
sortName: 'id',
|
||||
sortOrder: 'desc',
|
||||
columns: [
|
||||
[
|
||||
{checkbox: true},
|
||||
{field: 'id', title: 'ID', sortable: true},
|
||||
{field: 'enabled', title: __('Enabled'), searchList: {'0': '关闭', '1': '启用'},
|
||||
formatter: function(val) {
|
||||
return val == 1 ? '<span class="label label-success">启用</span>' : '<span class="label label-default">关闭</span>';
|
||||
}
|
||||
},
|
||||
{field: 'hours', title: __('Hours'), searchList: {'12': '近12小时', '24': '近24小时', '72': '近3天', '120': '近5天'},
|
||||
formatter: function(val) {
|
||||
var map = {'12': '近12小时', '24': '近24小时', '72': '近3天', '120': '近5天'};
|
||||
return '<span class="label label-info">' + (map[val] || val + '小时') + '</span>';
|
||||
}
|
||||
},
|
||||
{field: 'pinned_keywords', title: __('Pinned_keywords'), operate: 'LIKE',
|
||||
formatter: function(val) {
|
||||
try {
|
||||
var arr = JSON.parse(val || '[]');
|
||||
if (arr.length === 0) return '<span class="text-muted">无</span>';
|
||||
return arr.map(function(k){ return '<span class="label label-warning">' + k + '</span>'; }).join(' ');
|
||||
} catch(e) { return val || '-'; }
|
||||
}
|
||||
},
|
||||
{field: 'custom_keywords', title: __('Custom_keywords'), operate: 'LIKE',
|
||||
formatter: function(val) {
|
||||
try {
|
||||
var arr = JSON.parse(val || '[]');
|
||||
if (arr.length === 0) return '<span class="text-muted">无</span>';
|
||||
return arr.map(function(k){ return '<span class="label label-primary">' + k.keyword + '(' + k.count + ')</span>'; }).join(' ');
|
||||
} catch(e) { return val || '-'; }
|
||||
}
|
||||
},
|
||||
{field: 'updated_at', title: __('Updated_at'), sortable: true,
|
||||
formatter: function(val) {
|
||||
return val ? new Date(val * 1000).toLocaleString('zh-CN') : '-';
|
||||
}
|
||||
},
|
||||
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
// 清空热搜记录按钮
|
||||
$(document).on('click', '.btn-clear-hot', function() {
|
||||
Layer.confirm('确认清空所有热门搜索记录?此操作不可恢复。', function(index) {
|
||||
Fast.api.ajax('search_hot_config/clear_hot', function(data, ret) {
|
||||
Layer.close(index);
|
||||
Toastr.success(ret.msg || '清空成功');
|
||||
$(".btn-refresh").trigger("click");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 安装数据表按钮
|
||||
$(document).on('click', '.btn-install', function() {
|
||||
Layer.confirm('确认安装/初始化搜索热门配置数据表?', function(index) {
|
||||
Fast.api.ajax('search_hot_config/install', function(data, ret) {
|
||||
Layer.close(index);
|
||||
Toastr.success(ret.msg || '安装成功');
|
||||
$(".btn-refresh").trigger("click");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Table.api.bindevent(table);
|
||||
},
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
bindevent: function () {
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
Reference in New Issue
Block a user