This commit is contained in:
Developer
2026-06-13 09:07:07 +08:00
parent 82bb3e2f14
commit 906dcc9eb1
69 changed files with 6812 additions and 1254 deletions

View 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} 个热门搜索记录文件");
}
}

View File

@@ -0,0 +1,10 @@
<?php
return [
'Id' => 'ID',
'Enabled' => '是否启用',
'Hours' => '统计时长(小时)',
'Pinned_keywords' => '置顶关键词',
'Custom_keywords' => '自定义热词',
'Updated_at' => '更新时间',
];

View 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();
});
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
/**

View File

@@ -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 | 说明 |
|------|------|

View 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;
});