本次提交包含大量代码优化、功能新增与服务端配置更新: 1. 修复分析报告统计数据,调整CMake策略设置 2. 优化APP权限配置、编辑器与聊天界面组件 3. 更新依赖库版本与pubspec配置 4. 新增文件传输服务端、信令服务器相关配置与脚本 5. 完善用户注销功能与数据库迁移脚本 6. 优化多处动画效果、代码风格与日志输出 7. 新增多种调试与部署脚本,修复已知BUG
1904 lines
70 KiB
PHP
1904 lines
70 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use app\common\controller\Api;
|
||
use QL\QueryList;
|
||
use think\Db;
|
||
use think\Request;
|
||
use think\Image;
|
||
use think\Cache;
|
||
use DateTime;
|
||
|
||
class Webapi extends Api
|
||
{
|
||
protected $noNeedLogin = ['*'];
|
||
protected $noNeedRight = ['*'];
|
||
|
||
// 获取网站 TDK 信息
|
||
public function getTDK($url) {
|
||
// 记录接口开始时间
|
||
$startTime = microtime(true);
|
||
// 使用 QueryList 获取网页内容
|
||
$html = QueryList::get($url)->getHtml();
|
||
// 创建 QueryList 对象
|
||
$ql = QueryList::html($html)?QueryList::html($html):$this->error('未获取到网站信息');
|
||
$tdk = [];
|
||
$tdk['url'] = rtrim($url, '/');
|
||
// 获取页面中的ICO文件链接
|
||
$tdk['ico'] = $ql->find('link[rel*=icon]')->attrs('href');
|
||
// 网站名称
|
||
$tdk ['name'] = $ql->find('title')->text()?trim(tags($ql->find('title')->text())[0]):'未获取到标题';
|
||
// 获取页面标题
|
||
$tdk['title'] = $ql->find('title')->text();
|
||
// 获取页面关键词
|
||
$tdk['keywords'] = $ql->find('meta[name=keywords],meta[name=Keywords],meta[name=KeyWords],meta[http-equiv=keywords]')->attr('content');
|
||
// 获取页面描述
|
||
$tdk['description'] = $ql->find('meta[name=description],meta[name=Description],meta[http-equiv=description]')->attr('content');
|
||
if(empty($tdk['ico'])){
|
||
$tdk['ico'] = '未获取到ico';
|
||
}else{
|
||
if(@getimagesize($tdk['ico']) !== true){
|
||
$tdk['ico'] = str_replace(['[', ']', '\\', '"'], '', $tdk['url'].$tdk['ico']);
|
||
}
|
||
}
|
||
empty($tdk['title'])&&($tdk['title'] = '未获取到标题');
|
||
empty($tdk['keywords'])&&($tdk['keywords'] = $tdk['name']);
|
||
empty($tdk['description'])&&($tdk['description'] = '未获取到网站简介');
|
||
$tdk['keywords'] = implode(',', tags($tdk['keywords']));
|
||
// 记录接口结束时间
|
||
$endTime = microtime(true);
|
||
// 计算接口执行时间(单位:秒)
|
||
$tdk['execute'] = round($endTime - $startTime,2);
|
||
$this->success('成功',$tdk);
|
||
}
|
||
// 百度普通推送
|
||
public function BaiDuPuTongTuiSong()
|
||
{
|
||
$db = input('get.');
|
||
$db['urls'] = tags($db['urls']);
|
||
$api = 'http://data.zz.baidu.com/urls?site='.$db['url'].'&token='.$db['token'];
|
||
$ch = curl_init();
|
||
$options = [
|
||
CURLOPT_URL => $api,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POSTFIELDS => implode("\n", $db['urls']),
|
||
CURLOPT_HTTPHEADER => array('Content-Type: text/plain'),
|
||
];
|
||
curl_setopt_array($ch, $options);
|
||
$result = curl_exec($ch);
|
||
$result = json_decode($result,true);
|
||
if(isset($result['error'])){
|
||
$this->error('失败','失败:'.$result['message']);
|
||
}
|
||
$text = '剩余配额:'.$result['remain'].','.'成功推送:'.$result['success'];
|
||
$this->success('成功',$text);
|
||
}
|
||
// 必应普通推送
|
||
public function BingTuiSong()
|
||
{
|
||
$db = input('get.');
|
||
$db['urls'] = tags($db['urls']);
|
||
// 网站域名
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_URL, "https://ssl.bing.com//webmaster/api.svc/json/SubmitUrlbatch?apikey=".$db['token']);
|
||
curl_setopt($ch, CURLOPT_POST, 1);
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||
'siteUrl' => $db['url'],
|
||
'urlList' => $db['urls']
|
||
]));
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||
'Host: ssl.bing.com',
|
||
'Content-Type: application/json; charset=utf-8'
|
||
]);
|
||
$response = curl_exec($ch);
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
if ($http_code == 200 && json_decode($response)->d == null) {
|
||
$txt = "成功推送:".count($db['urls']).'条链接';
|
||
$this->error('推送成功',$txt);
|
||
} else {
|
||
$txt = "推送失败:推送配额不足!";
|
||
$this->error('推送失败',$txt);
|
||
}
|
||
}
|
||
// 神马推送
|
||
public function ShenMaTuiSong()
|
||
{
|
||
$db = input('get.');
|
||
$db['urls'] = tags($db['urls']);
|
||
$api = "https://data.zhanzhang.sm.cn/push?site=".$db['url']."&user_name=".$db['user']."&resource_name=mip_add&token=".$db['token'];
|
||
$ch = curl_init();
|
||
$options = [
|
||
CURLOPT_URL => $api,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POSTFIELDS => implode("\n", $db['urls']),
|
||
CURLOPT_HTTPHEADER => ['Content-Type: text/plain'],
|
||
];
|
||
curl_setopt_array($ch, $options);
|
||
$result = json_decode(curl_exec($ch));
|
||
if($result->returnCode == 200){
|
||
$this->success('成功',"成功推送:".count($db['urls']).'条');
|
||
}elseif ($result->returnCode == 201) {
|
||
$this->error('推送失败',"token不合法");
|
||
}elseif($result->returnCode == 202){
|
||
$this->error('推送失败',"配额已用尽!");
|
||
}elseif($result->returnCode == 400){
|
||
$this->error('推送失败',"请求参数有误");
|
||
}elseif($result->returnCode == 500){
|
||
$this->error('推送失败',"服务器内部错误");
|
||
}
|
||
$this->success('成功',$result->returnCode);
|
||
}
|
||
// 外链数据
|
||
public function linkData()
|
||
{
|
||
$urls = @file('links.txt');
|
||
if (!$urls) {
|
||
$this->error('链接文件不存在');
|
||
}
|
||
$count = count($urls);
|
||
$page = 1;
|
||
$num = 15;
|
||
|
||
$reqPage = $this->request->param('page', 1);
|
||
$reqNum = $this->request->param('num', 15);
|
||
if (is_numeric($reqPage) && $reqPage > 0) {
|
||
$page = intval($reqPage);
|
||
}
|
||
if (is_numeric($reqNum) && $reqNum <= 30 && $reqNum > 0) {
|
||
$num = intval($reqNum);
|
||
}
|
||
$maxpage = ceil($count / $num);
|
||
if ($page > $maxpage) {
|
||
$this->success('已完成', ['list' => [], 'count' => $count, 'page' => $page]);
|
||
} else {
|
||
$data = [];
|
||
foreach (array_slice($urls, ($page - 1) * $num, $num) as $value) {
|
||
$data[] = trim($value);
|
||
}
|
||
$this->success('成功', ['list' => $data, 'count' => $count, 'page' => $page, 'num' => $num]);
|
||
}
|
||
}
|
||
// 网站提交
|
||
public function submitYourSite()
|
||
{
|
||
$db = input('post.');
|
||
$data = [
|
||
'image' => $db['ico'],
|
||
'url' => $db['url'],
|
||
'name' => $db['name'],
|
||
'title' => $db['title'],
|
||
'keywords' => $db['keywords'],
|
||
'description' => $db['description'],
|
||
'category_id' => $db['category'],
|
||
'updatetime' => time(),
|
||
'createtime' => time()
|
||
];
|
||
foreach ($data as $value) {
|
||
if (empty($value)) {
|
||
$this->error($value.'不能为空!');
|
||
break;
|
||
}
|
||
}
|
||
$data['switch'] = 0;
|
||
$check = Db::name('site')->where('url','like','%'.urls($data['url']).'%')->value('id');
|
||
if(!$check){
|
||
$res = Db::name('site')->insert($data);
|
||
if($res){
|
||
$this->success('提交成功');
|
||
}else{
|
||
$this->error('提交失败');
|
||
}
|
||
}else{
|
||
$this->error('已提交过该网站!~');
|
||
}
|
||
}
|
||
// 网站点击量
|
||
public function siteHits()
|
||
{
|
||
$id = input('post.id');
|
||
if(empty($id) || !isset($id)){
|
||
$this->error('参数错误');
|
||
}
|
||
$data = Db::name('site')->where('id',$id)->setInc('hit');
|
||
if($data){
|
||
$this->success('成功');
|
||
}
|
||
$this->error('失败');
|
||
}
|
||
// 检测端口方法
|
||
function checkDomainPort($url,$port)
|
||
{
|
||
$portNames = [
|
||
80 => 'HTTP',
|
||
443 => 'HTTPS',
|
||
53 => 'DNS',
|
||
4000 => 'QQ Client',
|
||
994 => 'SMTP:SSL',
|
||
3389 => 'TERMINAL',
|
||
123 => 'NTP',
|
||
22 => 'SSH',
|
||
3306 => 'MYSQL',
|
||
8080 => 'WWW代理',
|
||
43 => 'Whois',
|
||
1433 => 'MSSQL/udp',
|
||
993 => 'IMAP:SSL',
|
||
995 => 'POP3:SSL',
|
||
21 => 'FTP',
|
||
23 => 'TELNET',
|
||
110 => 'POP3',
|
||
25 => 'SMTP',
|
||
143 => 'IMAP'
|
||
];
|
||
$portName = isset($portNames[$port]) ? $portNames[$port] : '未知';
|
||
$timeout = 3; // 设置超时时间
|
||
|
||
$start_time = microtime(true); // 记录开始时间
|
||
|
||
$fp = @fsockopen($url, $port, $errno, $errstr, $timeout);
|
||
$data = [
|
||
'port' => $port,
|
||
'portName' => $portName,
|
||
];
|
||
if ($fp) {
|
||
// 连接成功
|
||
fclose($fp);
|
||
$end_time = microtime(true); // 记录结束时间
|
||
$query_time = round(($end_time - $start_time) * 1000); // 计算查询时间(毫秒)
|
||
$data['result'] = '开启';
|
||
$data['responseTime'] = $query_time;
|
||
} else {
|
||
$end_time = microtime(true); // 记录结束时间
|
||
$query_time = round(($end_time - $start_time) * 1000); // 计算查询时间(毫秒)
|
||
$data['result'] = '关闭';
|
||
$data['responseTime'] = $query_time;
|
||
}
|
||
$this->success('成功',$data);
|
||
}
|
||
protected function is_china_ip($ip) {
|
||
$url = "https://api.ip.sb/geoip/$ip";
|
||
$json = file_get_contents($url);
|
||
$data = json_decode($json, true);
|
||
if (isset($data['country_code']) && $data['country_code'] === 'CN') {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
// 获取网页头部信息
|
||
public function GetHeaders()
|
||
{
|
||
$url = input('post.url');
|
||
if(!isset($url)){
|
||
$this->error('缺少参数');
|
||
}
|
||
$pattern = '/^(?:https?:\/\/)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/';
|
||
if(!preg_match($pattern, $url)){
|
||
$this->error('域名不合法!');
|
||
}
|
||
$ch = curl_init($url);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||
// 设置超时时间为 5 秒
|
||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||
$response = curl_exec($ch);
|
||
// 检查是否发生超时
|
||
if (curl_errno($ch) == CURLE_OPERATION_TIMEDOUT) {
|
||
$this->error('超时响应,请检查该网站是否正常打开');
|
||
}
|
||
// 获取头部信息
|
||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||
$header = substr($response, 0, $headerSize);
|
||
|
||
// 打印头部信息
|
||
$this->success('获取成功',$header);
|
||
|
||
curl_close($ch);
|
||
}
|
||
// 随机生成User Agent信息
|
||
public function RandUA()
|
||
{
|
||
$db = input('post.');
|
||
$userAgents = [];
|
||
if(!isset($db['count'])){
|
||
$this->error('缺少参数');
|
||
}
|
||
if(isset($db['type'])){
|
||
for ($i = 0; $i < (int)$db['count']; $i++) {
|
||
$userAgents[] = \Campo\UserAgent::random(['os_type' => $db['type']]);
|
||
}
|
||
}else{
|
||
for ($i = 0; $i < (int)$db['count']; $i++) {
|
||
$userAgents[] = \Campo\UserAgent::random();
|
||
}
|
||
}
|
||
$this->success('生成成功',$userAgents);
|
||
}
|
||
// 图片压缩
|
||
public function ImgCompress()
|
||
{
|
||
// 获取表单上传文件
|
||
$file = request()->file('file');
|
||
$db = input('post.');
|
||
if(!isset($db['quality']) || $db['quality'] < 0){
|
||
$db['quality'] = 80;//质量
|
||
}
|
||
if(empty($db['width']) || $db['width'] < 0){
|
||
$db['width'] = 0;
|
||
}
|
||
if(empty($db['height']) || $db['height'] < 0){
|
||
$db['height'] = 0;
|
||
}
|
||
// 校验规则
|
||
$validateRule = ['image' => 'require|image|fileExt:jpg,png,gif'];
|
||
//校验器,判断图片格式是否正确
|
||
if (true !== $this->validate(['image' => $file], $validateRule)) {
|
||
$this->error('图片格式');
|
||
} else {
|
||
// 移动到框架应用根目录/public/compress/目录下
|
||
$info = $file->rule('uniqid')->move(ROOT_PATH . 'public' . DS . 'compress');
|
||
if ($info) {
|
||
$res = [];
|
||
// 存入相对路径 /compress/文件名
|
||
$data = DS . 'compress' . DS . $info->getSaveName();
|
||
$res['image'] = $data;
|
||
// 压缩前图片大小
|
||
$res['raw'] = round(filesize('.' . $data) / 1024, 2) . 'KB';
|
||
// 压缩图片判断
|
||
if(compressedImage('.'.$res['image'],(int)$db['width'],(int)$db['height'],(int)$db['quality'])){
|
||
// 执行刷新操作
|
||
clearstatcache(true, '.'.$res['image']);
|
||
// 压缩后图片大小
|
||
$res['queen'] = round(filesize('.' . $res['image']) / 1024, 2) . 'KB';
|
||
$res['width'] = $db['width'];
|
||
$res['height']= $db['height'];
|
||
$res['quality']= $db['quality'];
|
||
$this->success('压缩成功',$res);
|
||
}else{
|
||
$this->error('压缩失败');
|
||
}
|
||
} else {
|
||
// 上传失败获取错误信息
|
||
$this->error('错误信息',$file->getError());
|
||
}
|
||
}
|
||
}
|
||
// ocr图片识别
|
||
public function ocr()
|
||
{
|
||
if(Cache::has('cacheKey')){
|
||
$count = Cache::get('cacheKey');
|
||
}else{
|
||
$count = 0;
|
||
}
|
||
// 若超过最大调用次数,返回错误信息
|
||
if ($count >= 66) {
|
||
$this->error('今日接口调用次数已达上限,明天再来吧!');
|
||
}
|
||
// 获取上传图片文件
|
||
$file = request()->file('file');
|
||
// 移动到框架应用根目录/public/compress/目录下
|
||
$data ="";
|
||
$info = $file->rule('uniqid')->move(ROOT_PATH . 'public' . DS . 'ocr');
|
||
if($info){
|
||
// 存入相对路径 /compress/文件名
|
||
$data = '.'.DS . 'ocr' . DS . $info->getSaveName();
|
||
}else{
|
||
// 上传失败获取错误信息
|
||
$this->error('错误信息',$file->getError());
|
||
die;
|
||
}
|
||
$curl = curl_init();
|
||
curl_setopt_array($curl, [
|
||
CURLOPT_URL => "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic?access_token={$this->getAccessToken()}",
|
||
CURLOPT_TIMEOUT => 30,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_SSL_VERIFYHOST => false,
|
||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||
|
||
CURLOPT_POSTFIELDS => 'image='.urlencode(base64_encode(file_get_contents($data))).'&detect_direction=false¶graph=false&probability=false',
|
||
|
||
CURLOPT_HTTPHEADER => array(
|
||
'Content-Type: application/x-www-form-urlencoded',
|
||
'Accept: application/json'
|
||
),
|
||
]);
|
||
$response =curl_exec($curl);
|
||
curl_close($curl);
|
||
$res = json_decode($response,true);
|
||
// 增加接口调用次数计数
|
||
$count++;
|
||
Cache::set('cacheKey', $count, 86400); // 保存一天
|
||
if(isset($res['words_result'])){
|
||
$this->success('识别成功',$res['words_result']);
|
||
}else{
|
||
$this->error('识别错误',$response);
|
||
}
|
||
}
|
||
// ocr图片识别授权方法
|
||
private function getAccessToken(){
|
||
$API_KEY = "BctyMxvfj4HAFCr6iCHIxUa9";
|
||
$SECRET_KEY = "zvEGiF1isnBQ0UiiU32sAcv1EfwPlpPT";
|
||
$curl = curl_init();
|
||
$postData = array(
|
||
'grant_type' => 'client_credentials',
|
||
'client_id' => $API_KEY,
|
||
'client_secret' => $SECRET_KEY
|
||
);
|
||
curl_setopt_array($curl, array(
|
||
CURLOPT_URL => 'https://aip.baidubce.com/oauth/2.0/token',
|
||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_SSL_VERIFYHOST => false,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POSTFIELDS => http_build_query($postData)
|
||
));
|
||
$response = curl_exec($curl);
|
||
curl_close($curl);
|
||
$rtn = json_decode($response);
|
||
return $rtn->access_token;
|
||
}
|
||
// 窗帘计算器
|
||
public function curtain()
|
||
{
|
||
$db = input('post.');
|
||
if(empty($db['ch']) || empty($db['cw']) || empty($db['cn']) || empty($db['clw']) || empty($db['clp'])){
|
||
$this->error('参数错误!');
|
||
}
|
||
// 输入参数
|
||
$windowHeight = $db['ch']; // 窗户高度(米)
|
||
$windowWidth = $db['cw']; // 窗户宽度(米)
|
||
$windowCount = $db['cn']; // 窗户数量
|
||
$fabricWidth = $db['clw']; // 布料宽度(米)
|
||
$fabricPrice = $db['clp']; // 布料单价(元/米)
|
||
|
||
// 计算布料数量
|
||
$totalFabricLength = ((floatval($windowWidth) + floatval(0.15) * 2) * 2) / $fabricWidth
|
||
* (floatval(0.15) + floatval($windowHeight) + floatval(0.5) + floatval(0.2)) * $windowCount;
|
||
$clothCount = ceil($totalFabricLength);
|
||
|
||
// 计算布料总价
|
||
$clothTotal = $clothCount * $fabricPrice;
|
||
|
||
// 格式化金额
|
||
function MilliFormat($money){
|
||
return number_format($money, 2);
|
||
}
|
||
|
||
$clothTotalFormatted = MilliFormat($clothTotal);
|
||
$data['clothCount'] = $clothCount;
|
||
$data['clothTotalFormatted'] = $clothTotalFormatted;
|
||
$this->success('计算成功',$data);
|
||
}
|
||
// 地砖计算器
|
||
public function tiles()
|
||
{
|
||
$db = input('post.');
|
||
if(empty($db['rl']) || empty($db['rw']) || empty($db['tl']) || empty($db['tw']) || empty($db['tp'])){
|
||
$this->error('参数错误!');
|
||
}
|
||
$roomLength = $db['rl'];
|
||
$roomWidth = $db['rw'];
|
||
$tileLength = $db['tl'];
|
||
$tileWidth = $db['tw'];
|
||
$tilePrice = $db['tp'];
|
||
|
||
$roomNum = 0;
|
||
$roomTotal = 0;
|
||
$rate = 1.05;
|
||
|
||
$jgspanArea = $roomLength * $roomWidth;
|
||
$jgspanArea = number_format($jgspanArea, 2);
|
||
|
||
$roomLength = $roomLength * 1000;
|
||
$roomWidth = $roomWidth * 1000;
|
||
|
||
$roomNum = ceil(($roomLength / $tileLength) * ($roomWidth / $tileWidth) * $rate);
|
||
$roomTotal = $roomNum * $tilePrice;
|
||
$roomTotal = number_format($roomTotal, 2);
|
||
|
||
$data['roomNum'] = $roomNum;
|
||
$data['roomTotal'] = $roomTotal;
|
||
$this->success('计算成功',$data);
|
||
}
|
||
// 壁纸计算器
|
||
public function wallpaper()
|
||
{
|
||
$db = input('post.');
|
||
if(empty($db['rh']) || empty($db['rl']) || empty($db['rn']) || empty($db['rv']) || empty($db['rp'])){
|
||
$this->error('参数错误!');
|
||
}
|
||
$room_height = $db['rh']; // 墙面高度
|
||
$room_long = $db['rl']; // 墙面长度
|
||
$room_num = $db['rn']; // 墙面数量
|
||
$room_vol = $db['rv']; // 壁纸规格(平方米/卷)
|
||
$room_price = $db['rp']; // 壁纸单价(元/卷)
|
||
|
||
// 计算壁纸数量和总价
|
||
$room_block = ceil($room_long * $room_height * $room_num * 1.1 / $room_vol);
|
||
$room_total = $room_block * $room_price;
|
||
$room_total = number_format($room_total, 2);
|
||
|
||
$data['room_block'] = $room_block;
|
||
$data['room_total'] = $room_total;
|
||
$this->success('计算成功',$data);
|
||
}
|
||
// 地板计算器
|
||
public function floors()
|
||
{
|
||
$db = input('post.');
|
||
if(empty($db['fl']) || empty($db['fw']) || empty($db['fll']) || empty($db['flw']) || empty($db['fp'])){
|
||
$this->error('参数错误!');
|
||
}
|
||
// 输入数据
|
||
$room_length = $db['fl']; // 房间长度
|
||
$room_width = $db['fw']; // 房间宽度
|
||
$floor_length = $db['fll']; // 地板长度
|
||
$floor_width = $db['flw']; // 地板宽度
|
||
$floor_type = empty($db['ft'])?1.08:1.05; // 地板类型(0=实木地板或1=复合地板)
|
||
$floor_price = $db['fp']; // 地板单价(元/块)
|
||
|
||
// 计算房间面积
|
||
$jgspan_area = $room_length * $room_width;
|
||
|
||
// 将单位转换为毫米
|
||
$room_length *= 1000;
|
||
$room_width *= 1000;
|
||
|
||
// 计算地板数量和总价
|
||
$floornum = ceil(($room_length / $floor_length) * ($room_width / $floor_width) * $floor_type);
|
||
$floorprice = $floornum * $floor_price;
|
||
|
||
$data['floornum'] = number_format($floornum, 0); // 这里重新将结果格式化为字符串
|
||
$data['floorprice'] = number_format($floorprice, 2); // 这里重新将结果格式化为字符串
|
||
if(empty($data)){
|
||
$this->error('计算失败');
|
||
}
|
||
$this->success('计算成功', $data);
|
||
}
|
||
// 墙砖计算器
|
||
public function brick()
|
||
{
|
||
$db = input('post.');
|
||
if(empty($db['bh']) || empty($db['rl']) || empty($db['rw']) || empty($db['dh']) || empty($db['dw']) || empty($db['dn']) || empty($db['wh']) || empty($db['ww']) || empty($db['wn']) || empty($db['bl']) || empty($db['bw']) || empty($db['bp'])){
|
||
$this->error('参数错误!');
|
||
}
|
||
|
||
$wall_height = $db['bh']; // 墙面高度
|
||
$room_long = $db['rl']; // 房间长度
|
||
$room_width = $db['rw']; // 房间宽度
|
||
$door_height = $db['dh']; // 房门高度
|
||
$door_width = $db['dw']; // 房门宽度
|
||
$door_num = $db['dn']; // 门的数量
|
||
$window_height = $db['wh']; // 窗户高度
|
||
$window_width = $db['ww']; // 窗户宽度
|
||
$window_num = $db['wn']; // 窗户数量
|
||
$brick_long = $db['bl']; // 砖块长度/毫米
|
||
$brick_width = $db['bw']; // 砖块宽度/毫米
|
||
$brick_price = $db['bp']; // 砖块单价
|
||
|
||
$room_num = 0;
|
||
$room_total = 0;
|
||
|
||
$room_long = $room_long*1000;
|
||
$wall_height = $wall_height*1000;
|
||
$room_width = $room_width*1000;
|
||
$door_height = $door_height*1000;
|
||
$door_width = $door_width*1000;
|
||
$window_height = $window_height*1000;
|
||
$window_width = $window_width*1000;
|
||
|
||
$room_num = ($room_long/$brick_long)*($wall_height/$brick_width)*2;
|
||
$room_num = floatval($room_num)+floatval(($room_width/$brick_long)*($wall_height/$brick_width)*2);
|
||
$room_num = floatval($room_num)-floatval(($window_height/$brick_long)*($window_width/$brick_width)*$window_num);
|
||
$room_num = floatval($room_num)-floatval(($door_height/$brick_long)*($door_width/$brick_width)*$door_num);
|
||
$room_num = ceil($room_num*1.05);
|
||
$room_total = $room_num*floatval($brick_price);
|
||
$room_total = number_format($room_total, 2, '.', ',');
|
||
|
||
$data['room_num'] = $room_num?$room_num:null;
|
||
$data['room_total'] = $room_total?$room_total:null;
|
||
if(empty($data)){
|
||
$this->error('计算失败');
|
||
}
|
||
$this->success('计算成功',$data);
|
||
}
|
||
// 涂料计算器
|
||
public function paint()
|
||
{
|
||
$db = input('post.');
|
||
if(empty($db['ph']) || empty($db['rh']) || empty($db['rw']) || empty($db['dh']) || empty($db['dw']) || empty($db['dn']) || empty($db['wh']) || empty($db['ww']) || empty($db['wn']) || empty($db['cover']) || empty($db['pp'])){
|
||
$this->error('参数错误!');
|
||
}
|
||
|
||
$room_long = $db['ph']; // 墙面长度(米)
|
||
$wall_height = $db['rh']; // 房间长度(米)
|
||
$room_width = $db['rw']; // 房间宽度(米)
|
||
$door_height = $db['dh']; // 门高度(米)
|
||
$door_width = $db['dw']; // 门宽度(米)
|
||
$door_num = $db['dn']; // 门数量(个)
|
||
$window_height = $db['wh']; // 窗户高度(米)
|
||
$window_width = $db['ww']; // 窗户宽度(米)
|
||
$window_num = $db['wn']; // 窗户数量(个)
|
||
$paint_cover = $db['cover']; // 涂料覆盖率(平方米/升)
|
||
$room_price = $db['pp']; // 涂料的价格(元/升)
|
||
|
||
$room_num = 0;
|
||
$room_total = 0;
|
||
|
||
// 计算房间面积
|
||
$room_area = ($room_long + $room_width) * 2 * $wall_height + ($room_long * $room_width);
|
||
$room_area -= ($door_height * $door_width * $door_num);
|
||
$room_area -= ($window_height * $window_width * $window_num);
|
||
|
||
// 计算需要的涂料总量
|
||
$room_num = round($room_area / $paint_cover, 2);
|
||
|
||
// 计算涂料费用
|
||
$room_total = number_format($room_num * $room_price, 2);
|
||
|
||
$data['room_num'] = $room_num?$room_num:null;
|
||
$data['room_total'] = $room_total?$room_total:null;
|
||
if(empty($data)){
|
||
$this->error('计算失败');
|
||
}
|
||
$this->success('计算成功',$data);
|
||
}
|
||
// 提交补充纠错接口(旧版,保留兼容)
|
||
public function perfectData()
|
||
{
|
||
$content = input('post.content');
|
||
$mail = input('post.mail');
|
||
if(empty($content)){
|
||
$this->error('参数错误!'); // 参数为空的情况
|
||
}
|
||
if(mb_strlen($content) <= 10){ // 使用mb_strlen进行多字节字符长度判断
|
||
$this->error('内容过少,请重新编辑!'); // 内容过少的情况
|
||
}
|
||
$data = [
|
||
'content' => nl2br($content),
|
||
'mail' => $mail?$mail:null,
|
||
'switch' => 0,
|
||
'ip' => Request::instance()->ip(),
|
||
'updatetime' => time(),
|
||
'createtime' => time()
|
||
];
|
||
$result = Db::name('perfect')->insert($data); // 插入数据
|
||
if($result){
|
||
$this->success('提交成功,等待管理员修正问题!');
|
||
} else {
|
||
$this->error('补充纠错失败,稍后再试!'); // 插入失败的情况
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @name 纠错提交API(新版,支持来源信息)
|
||
* @desc 独立纠错接口,保留来源页面字段,用于App和独立页面调用
|
||
* @param string content 纠错内容(必填,10-2000字)
|
||
* @param string mail 联系邮箱(可选)
|
||
* @param string source_url 来源页面URL(可选)
|
||
* @param string source_type 数据类型如poetry/chengyu等(可选)
|
||
* @param int source_id 数据ID(可选)
|
||
*/
|
||
public function correction_submit()
|
||
{
|
||
$this->_apiCount('correction_submit');
|
||
$content = input('post.content', '', 'trim');
|
||
$mail = input('post.mail', '', 'trim');
|
||
$source_url = input('post.source_url', '', 'trim');
|
||
$source_type = input('post.source_type', '', 'trim');
|
||
$source_id = input('post.source_id', 0, 'intval');
|
||
|
||
if (empty($content)) {
|
||
$this->error('请输入纠错内容');
|
||
}
|
||
if (mb_strlen($content) < 10) {
|
||
$this->error('内容过少,请详细描述问题');
|
||
}
|
||
if (mb_strlen($content) > 2000) {
|
||
$this->error('内容过长,请控制在2000字以内');
|
||
}
|
||
|
||
$fullContent = '';
|
||
if (!empty($source_url)) {
|
||
$fullContent = '来源页面: ' . $source_url . "\n";
|
||
}
|
||
if (!empty($source_type) && $source_id > 0) {
|
||
$fullContent .= '数据类型: ' . $source_type . ' | ID: ' . $source_id . "\n";
|
||
}
|
||
$fullContent .= "\n" . $content;
|
||
|
||
$data = [
|
||
'content' => nl2br(htmlspecialchars($fullContent)),
|
||
'mail' => $mail ?: null,
|
||
'source_url' => $source_url ?: null,
|
||
'source_type' => $source_type ?: null,
|
||
'source_id' => $source_id > 0 ? $source_id : 0,
|
||
'switch' => 0,
|
||
'ip' => Request::instance()->ip(),
|
||
'updatetime' => time(),
|
||
'createtime' => time(),
|
||
];
|
||
|
||
$result = Db::name('perfect')->insert($data);
|
||
if ($result) {
|
||
$this->success('提交成功,感谢您的反馈!管理员会及时处理。');
|
||
} else {
|
||
$this->error('提交失败,请稍后再试');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @name 纠错记录列表API
|
||
* @desc 获取已处理的纠错记录列表
|
||
* @param int page 页码
|
||
* @param int limit 每页条数
|
||
* @param int status 状态 0待处理 1已处理
|
||
*/
|
||
public function correction_list()
|
||
{
|
||
$this->_apiCount('correction_list');
|
||
$page = input('get.page', 1, 'intval');
|
||
$limit = input('get.limit', 10, 'intval');
|
||
$status = input('get.status', -1, 'intval');
|
||
|
||
$query = Db::name('perfect');
|
||
if ($status >= 0) {
|
||
$query->where('switch', $status);
|
||
}
|
||
|
||
$total = $query->count();
|
||
$list = $query->order('createtime desc')
|
||
->page($page, $limit)
|
||
->field('id,content,mail,source_url,source_type,source_id,switch,ip,createtime,updatetime')
|
||
->select();
|
||
|
||
foreach ($list as &$item) {
|
||
$item['content'] = strip_tags($item['content']);
|
||
$item['status_text'] = $item['switch'] == 1 ? '已处理' : '待处理';
|
||
$item['createtime_text'] = date('Y-m-d H:i:s', $item['createtime']);
|
||
}
|
||
unset($item);
|
||
|
||
$this->success('成功', [
|
||
'total' => $total,
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'limit' => $limit,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* @name 用户注销申请API
|
||
* @desc 用户提交账号注销申请
|
||
* @param string token 用户登录token
|
||
* @param string reason 注销原因(可选)
|
||
*/
|
||
private function _getUserByToken($token)
|
||
{
|
||
if (empty($token)) {
|
||
return null;
|
||
}
|
||
$tokenData = \app\common\library\Token::get($token);
|
||
if (!$tokenData || !isset($tokenData['user_id'])) {
|
||
return null;
|
||
}
|
||
$user = Db::name('user')->where('id', $tokenData['user_id'])->find();
|
||
return $user;
|
||
}
|
||
|
||
public function user_deletion_apply()
|
||
{
|
||
$this->_apiCount('user_deletion_apply');
|
||
$token = input('post.token', '', 'trim');
|
||
if (empty($token)) {
|
||
$token = $this->request->header('token', '');
|
||
}
|
||
$reason = input('post.reason', '', 'trim');
|
||
|
||
if (empty($token)) {
|
||
$this->error('请先登录');
|
||
}
|
||
|
||
$user = $this->_getUserByToken($token);
|
||
if (!$user) {
|
||
$this->error('用户不存在或登录已过期');
|
||
}
|
||
|
||
$existing = Db::name('user_deletion')
|
||
->where('user_id', $user['id'])
|
||
->where('status', 0)
|
||
->find();
|
||
if ($existing) {
|
||
$this->error('您已有待审核的注销申请');
|
||
}
|
||
|
||
$threeDaysLater = time() + 3 * 24 * 3600;
|
||
|
||
$data = [
|
||
'user_id' => $user['id'],
|
||
'username' => $user['username'] ?? $user['nickname'] ?? '',
|
||
'reason' => $reason ?: '用户主动申请注销',
|
||
'status' => 0,
|
||
'admin_id' => 0,
|
||
'admin_remark' => '',
|
||
'auto_delete_time' => $threeDaysLater,
|
||
'createtime' => time(),
|
||
'updatetime' => time(),
|
||
'deletetime' => null,
|
||
];
|
||
|
||
$result = Db::name('user_deletion')->insert($data);
|
||
if ($result) {
|
||
$this->success('注销申请已提交,管理员3天内审核,未审核将自动注销');
|
||
} else {
|
||
$this->error('提交失败');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @name 用户注销状态查询API
|
||
* @desc 查询当前用户的注销申请状态
|
||
* @param string token 用户登录token
|
||
*/
|
||
public function user_deletion_status()
|
||
{
|
||
$this->_apiCount('user_deletion_status');
|
||
$token = input('get.token', '', 'trim');
|
||
if (empty($token)) {
|
||
$token = $this->request->header('token', '');
|
||
}
|
||
|
||
if (empty($token)) {
|
||
$this->error('请先登录');
|
||
}
|
||
|
||
$user = $this->_getUserByToken($token);
|
||
if (!$user) {
|
||
$this->error('用户不存在或登录已过期');
|
||
}
|
||
|
||
$record = Db::name('user_deletion')
|
||
->where('user_id', $user['id'])
|
||
->order('createtime desc')
|
||
->find();
|
||
|
||
if (!$record) {
|
||
$this->success('无注销申请', ['has_request' => false]);
|
||
}
|
||
|
||
$statusMap = [0 => '待审核', 1 => '已通过(已注销)', 2 => '已拒绝', 3 => '已自动注销'];
|
||
$record['status_text'] = $statusMap[$record['status']] ?? '未知';
|
||
$record['createtime_text'] = date('Y-m-d H:i:s', $record['createtime']);
|
||
$record['auto_delete_time_text'] = date('Y-m-d H:i:s', $record['auto_delete_time']);
|
||
|
||
$this->success('成功', ['has_request' => true, 'record' => $record]);
|
||
}
|
||
|
||
/**
|
||
* @name 用户撤销注销申请API
|
||
* @desc 用户撤销待审核的注销申请
|
||
* @param string token 用户登录token
|
||
*/
|
||
public function user_deletion_cancel()
|
||
{
|
||
$this->_apiCount('user_deletion_cancel');
|
||
$token = input('post.token', '', 'trim');
|
||
if (empty($token)) {
|
||
$token = $this->request->header('token', '');
|
||
}
|
||
|
||
if (empty($token)) {
|
||
$this->error('请先登录');
|
||
}
|
||
|
||
$user = $this->_getUserByToken($token);
|
||
if (!$user) {
|
||
$this->error('用户不存在或登录已过期');
|
||
}
|
||
|
||
$record = Db::name('user_deletion')
|
||
->where('user_id', $user['id'])
|
||
->where('status', 0)
|
||
->find();
|
||
|
||
if (!$record) {
|
||
$this->error('没有待审核的注销申请');
|
||
}
|
||
|
||
$result = Db::name('user_deletion')
|
||
->where('id', $record['id'])
|
||
->update(['status' => 2, 'admin_remark' => '用户主动撤销', 'updatetime' => time()]);
|
||
|
||
if ($result) {
|
||
$this->success('已撤销注销申请');
|
||
} else {
|
||
$this->error('撤销失败');
|
||
}
|
||
}
|
||
|
||
// 验证验证码
|
||
public function checkCaptcha()
|
||
{
|
||
$code = input('post.code');
|
||
if (captcha_check($code)) {
|
||
$this->success('验证码正确');
|
||
} else {
|
||
$this->error('验证码错误!');
|
||
}
|
||
}
|
||
// 综合热榜
|
||
public function HotRefresh()
|
||
{
|
||
$id = input('post.t');
|
||
if(empty($id) && !isset($id)){
|
||
$this->error('参数错误');
|
||
}
|
||
$way = Db::name('hot')->where('id',$id)->value('way');
|
||
if($way){
|
||
$result = action($way);
|
||
if(strpos($result, "写入失败") !== false){
|
||
$this->error($result);
|
||
}
|
||
if(strpos($result, "写入成功") !== false){
|
||
$this->success($result);
|
||
}
|
||
if(strpos($result, "采集失败") !== false){
|
||
$this->error($result);
|
||
}
|
||
}else{
|
||
$this->error('参数错误');
|
||
}
|
||
}
|
||
// 图片转base64
|
||
public function ImageToBase()
|
||
{
|
||
// 获取上传的文件
|
||
$file = request()->file('image');
|
||
|
||
$type = $file->getInfo('type');
|
||
// 移动上传的文件到临时目录
|
||
$info = $file->move(ROOT_PATH . 'public' . DS . 'base64');
|
||
if (!$info) {
|
||
// 上传失败,返回错误信息
|
||
$this->error('上传失败',$file->getError());
|
||
}
|
||
// 获取文件路径
|
||
$filePath = './base64'. DS .$info->getSaveName();
|
||
// 将图片转换为Base64编码
|
||
$base64 = base64_encode(file_get_contents($filePath));
|
||
|
||
if(empty($base64)){
|
||
$this->error('转换失败');
|
||
}
|
||
$this->success('转换成功','data:'.$type.';base64,'.$base64);
|
||
}
|
||
// 爬虫模拟抓取工具
|
||
public function crawlerTool()
|
||
{
|
||
// 获取输入
|
||
$url = input('post.url');
|
||
$userAgent = input('post.userAgent');
|
||
|
||
// 简单的输入验证
|
||
if (!isset($url) || !isset($userAgent)) {
|
||
$this->error('参数缺少','请提供URL和User-Agent');
|
||
}
|
||
|
||
// 初始化cURL会话
|
||
$ch = curl_init($url);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
curl_setopt($ch, CURLOPT_HEADER, true); // 包含头部信息
|
||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
|
||
|
||
// 执行cURL会话
|
||
$response = curl_exec($ch);
|
||
$curl_errno = curl_errno($ch);
|
||
$curl_error = curl_error($ch);
|
||
|
||
// 获取头部大小
|
||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||
|
||
// 关闭cURL会话
|
||
curl_close($ch);
|
||
|
||
if ($curl_errno) {
|
||
$this->error("页面抓取错误", " ({$curl_errno}): {$curl_error}");
|
||
}
|
||
|
||
// 根据headerSize分割响应的头部和内容
|
||
$header = substr($response, 0, $headerSize);
|
||
$body = substr($response, $headerSize);
|
||
|
||
// 如果需要的话,可以对内容进行编码转换
|
||
// 这一步骤可能不是必须的,取决于响应本身的编码
|
||
$body = mb_convert_encoding($body, 'UTF-8', mb_detect_encoding($body));
|
||
|
||
// 使用响应主体的长度来获取大小,变量$body已经包含了内容
|
||
$contentSizeBytes = strlen($body);
|
||
|
||
// 将大小转换为 kilobytes (KB)
|
||
$contentSizeKb = $contentSizeBytes / 1024;
|
||
$data = [
|
||
'head' => trim($header),
|
||
'content' => $body,
|
||
'size' => round($contentSizeKb, 2).'KB'
|
||
];
|
||
|
||
// 返回成功响应
|
||
$this->success('页面抓取成功', $data);
|
||
}
|
||
// 在线生成ICO图标
|
||
public function favicons()
|
||
{
|
||
$data['upmsg'] = '';
|
||
if (request()->isPost()) {
|
||
$upimage = input('file.upimage');
|
||
$getInfo = $upimage->getInfo();
|
||
if (isset($getInfo['tmp_name']) && $getInfo['tmp_name'] && is_uploaded_file($getInfo['tmp_name'])) {
|
||
if ((int)$getInfo['size'] > 2048000) {
|
||
return "<font color=\"red\">你上传的文件体积超过了限制 最大不能超过1MB</font><script>setTimeout(function() { history.back(); }, 2000);</script>";
|
||
} else {
|
||
$fileext = array("image/pjpeg", "image/gif", "image/x-png", "image/png", "image/jpeg", "image/jpg");
|
||
if (!in_array($getInfo['type'], $fileext)) {
|
||
return "<font color=\"red\">你上传的文件格式不正确 仅支持 jpg,gif,png</font><script>setTimeout(function() { history.back(); }, 2000);</script>";
|
||
} else {
|
||
$type = substr(strrchr($getInfo['name'], '.'), 1);
|
||
switch ($type) {
|
||
case 'pjpeg':
|
||
case 'jpeg':
|
||
case 'jpg':
|
||
$im = imagecreatefromjpeg($getInfo['tmp_name']);
|
||
break;
|
||
case 'x-png':
|
||
case 'png':
|
||
$im = imagecreatefrompng($getInfo['tmp_name']);
|
||
break;
|
||
case 'gif':
|
||
$im = imagecreatefromgif($getInfo['tmp_name']);
|
||
break;
|
||
default:
|
||
$im = null;
|
||
}
|
||
if ($im) {
|
||
$imginfo = getimagesize($getInfo['tmp_name']);
|
||
if (!is_array($imginfo)) {
|
||
return "<font color=\"red\">图形格式错误!</font><script>setTimeout(function() { history.back(); }, 2000);</script>";
|
||
} else {
|
||
switch (input('favicon_size')) {
|
||
case 1;
|
||
$resize_im = imagecreatetruecolor(16, 16);
|
||
$size = 16;
|
||
break;
|
||
case 2;
|
||
$resize_im = imagecreatetruecolor(32, 32);
|
||
$size = 32;
|
||
break;
|
||
case 3;
|
||
$resize_im = imagecreatetruecolor(48, 48);
|
||
$size = 48;
|
||
break;
|
||
case 4;
|
||
$resize_im = imagecreatetruecolor(64, 64);
|
||
$size = 64;
|
||
break;
|
||
case 5;
|
||
$resize_im = imagecreatetruecolor(128, 128);
|
||
$size = 128;
|
||
break;
|
||
default;
|
||
$resize_im = imagecreatetruecolor(32, 32);
|
||
$size = 32;
|
||
break;
|
||
}
|
||
imagecopyresampled($resize_im, $im, 0, 0, 0, 0, $size, $size, $imginfo[0], $imginfo[1]);
|
||
$icon = new \Net\Ico();
|
||
$gd_image_array = array($resize_im);
|
||
$icon_data = $icon->GD2ICOstring($gd_image_array);
|
||
header("Accept-Ranges: bytes");
|
||
header("Accept-Length: " . strlen($icon_data));
|
||
header("Content-type: application/octet-stream");
|
||
header("Content-Disposition: attachment; filename=" . 'favicon.ico');
|
||
return $icon_data;
|
||
}
|
||
} else {
|
||
return "<font color=\"red\">生成错误请重试!</font><script>setTimeout(function() { history.back(); }, 2000);</script>";
|
||
}
|
||
}
|
||
}
|
||
}else{
|
||
return "<font color=\"red\">请上传图片</font><script>setTimeout(function() { history.back(); }, 2000);</script>";
|
||
}
|
||
}
|
||
}
|
||
// 赞赏链接跳转方法
|
||
public function reward_link()
|
||
{
|
||
$id = input('post.id');
|
||
$url = Db::name('appreciation')->where('id',$id)->where('switch',1)->value('url');
|
||
if(empty($url)){
|
||
$this->error('不存在链接!');
|
||
}
|
||
Db::name('appreciation')->where('id',$id)->setInc('hits');
|
||
$this->success('跳转',$url.'?rel='.Request::instance()->domain());
|
||
}
|
||
// ip地址查下
|
||
public function ip()
|
||
{
|
||
$ip = input('post.ip');
|
||
if (!$ip) {
|
||
$ip = $this->request->ip();
|
||
}
|
||
$ips = new \Net\Ips('./qqwry.dat');
|
||
$data['ip'] = $ip;
|
||
$data['domain'] = gethostbyname($ip);
|
||
$domain = preg_replace('/(\d+)..*/', '\\1', $data['domain']);
|
||
if ('1' <= $domain && $domain <= '126') {
|
||
$data['fw'] = '1.0.0.1 - 126.155.255.254';
|
||
} elseif ('128' <= $domain && $domain <= '191') {
|
||
$data['fw'] = '128.0.0.1 - 191.255.255.254';
|
||
} elseif ('192' <= $domain && $domain <= '223') {
|
||
$data['fw'] = '192.0.0.1 - 223.255.255.254';
|
||
}
|
||
$fwq = $ips->Getlocation($data['domain']);
|
||
$data['city'] = strToUTF8($fwq['country'] . ' ' . $fwq['area']);
|
||
$data['num'] = ip2long($data['domain']);
|
||
if($data){
|
||
$this->success('查询成功',$data);
|
||
}
|
||
$this->error('查询错误');
|
||
}
|
||
// 单页采集
|
||
public function pages()
|
||
{
|
||
$url = input('post.url');
|
||
if (empty($url)) {
|
||
$this->error('请提供采集地址(url参数)');
|
||
}
|
||
$urls = urls($url);
|
||
// iconv('GBK','UTF-8//IGNORE',file_get_contents($url))
|
||
$ql = QueryList::get($url,[],[
|
||
'timeout'=>30,//超时设置
|
||
'headers'=>[
|
||
'User-Agent'=>'Mozilla/5.0(Windows NT 10.0;Win64; x64)AppleWebkit/537.36(KHTML,like Gecko) Chrome/80.0.3987.163 Safari/537.36',
|
||
|
||
]
|
||
]);
|
||
$res = Db::name('gather')->where('url',"like","%".$urls."%")->field('rule,tag,classify')->find();//获取采集规则
|
||
if($res){
|
||
// encoding('UTF-8','GB2312')->removeHead()->
|
||
$data = $ql->rules(json_decode($res['rule'],true))->queryData();//采集开始
|
||
if(!empty($data)){
|
||
// 查询分类名
|
||
$cla = Db::name('category')->where('id',$res['classify'])->field('id,name')->find();
|
||
$data['sort'] = $cla;//赋值分类ID
|
||
if(!empty($res['tag'])){
|
||
$keys = explode(",",$res['tag']);
|
||
foreach ($keys as $value){
|
||
$data['content'] = str_ireplace($value,'',$data['content']);
|
||
}
|
||
}
|
||
// if(isset($data['title'])){
|
||
// $data['title'] = str_replace("作文", "", $data['title']);
|
||
// }
|
||
if(isset($data['content']) && !empty($data['content'])){
|
||
if (strpos($data['content'], '<img') !== false) {
|
||
// 使用正则表达式替换图片标签中的相对地址
|
||
$pattern = "/<img(.*?)src=[\"'](.*?)[\"'](.*?)>/i";
|
||
$data['content'] = preg_replace_callback($pattern, function($matches) use ($url) {
|
||
$imageUrl = $matches[2];
|
||
if (preg_match('/^([a-z]+:\/\/[^\/]+)/i', $url, $matche)) {
|
||
$http = $matche[1];
|
||
} else {
|
||
$http = '';
|
||
}
|
||
// 判断图片地址是否为相对地址
|
||
if (strpos($imageUrl, 'http') !== 0) {
|
||
// 将相对地址转换为绝对地址
|
||
return "<img{$matches[1]}src='" . $http . "/$imageUrl'{$matches[3]}>";
|
||
} else {
|
||
return "<img{$matches[1]}src='{$imageUrl}'{$matches[3]}>";
|
||
}
|
||
}, $data['content']);
|
||
}
|
||
$data['content'] = preg_replace('/<p>\s*<\/p>/', '', $data['content']);
|
||
$data['content'] = preg_replace('/<p>\s*<br\s*\/>\s*<\/p>/', '', $data['content']);///<p>\s*<br \/>\s*<\/p>/
|
||
}
|
||
$this->success('成功',$data);
|
||
}else{
|
||
$this->error('失败',$data);
|
||
}
|
||
}else{
|
||
$this->error('不存在采集规则');
|
||
}
|
||
}
|
||
// 生成网页快捷方式
|
||
public function create()
|
||
{
|
||
// 获取传递过来的参数值
|
||
$db = input('get.');
|
||
|
||
// 拼接快捷方式的文件路径和名称
|
||
$file_path = './shortcut/' . $db['name'] . '.url';
|
||
|
||
// 生成快捷方式文件内容
|
||
$content = "[InternetShortcut]\r\nURL={$db['url']}";
|
||
|
||
// 将文件内容写入到指定路径的文件中
|
||
file_put_contents($file_path, $content);
|
||
|
||
// 设置响应头信息
|
||
header('Content-Type: application/octet-stream');
|
||
header('Content-Disposition: attachment; filename=' . $db['name'] . '.url');
|
||
header('Content-Length: ' . filesize($file_path));
|
||
|
||
// 输出文件并删除生成的快捷方式文件
|
||
readfile($file_path);
|
||
// unlink($file_path);
|
||
}
|
||
|
||
/**
|
||
* TTS文字转语音接口
|
||
* 基于浏览器端Web Speech API,此接口提供语音参数配置和文本预处理
|
||
*/
|
||
public function ttsConfig()
|
||
{
|
||
$text = input('post.text', '', 'trim');
|
||
$rate = input('post.rate', 1.0, 'floatval');
|
||
$pitch = input('post.pitch', 1.0, 'floatval');
|
||
$volume = input('post.volume', 1.0, 'floatval');
|
||
$lang = input('post.lang', 'zh-CN', 'trim');
|
||
|
||
if (empty($text)) {
|
||
$this->error('请输入要转换的文本');
|
||
}
|
||
if (mb_strlen($text) > 5000) {
|
||
$this->error('文本长度不能超过5000字');
|
||
}
|
||
if ($rate < 0.5 || $rate > 2.0) {
|
||
$this->error('语速范围: 0.5-2.0');
|
||
}
|
||
if ($pitch < 0.5 || $pitch > 2.0) {
|
||
$this->error('音调范围: 0.5-2.0');
|
||
}
|
||
if ($volume < 0 || $volume > 1.0) {
|
||
$this->error('音量范围: 0-1.0');
|
||
}
|
||
|
||
$allowedLangs = ['zh-CN', 'zh-TW', 'en-US', 'en-GB', 'ja-JP', 'ko-KR', 'fr-FR', 'de-DE', 'es-ES', 'ru-RU'];
|
||
if (!in_array($lang, $allowedLangs)) {
|
||
$this->error('不支持的语言: ' . $lang);
|
||
}
|
||
|
||
$text = strip_tags($text);
|
||
$text = htmlspecialchars_decode($text, ENT_QUOTES);
|
||
|
||
$this->success('配置成功', [
|
||
'text' => $text,
|
||
'rate' => $rate,
|
||
'pitch' => $pitch,
|
||
'volume' => $volume,
|
||
'lang' => $lang,
|
||
'charCount' => mb_strlen($text),
|
||
'engine' => 'Web Speech API',
|
||
]);
|
||
}
|
||
|
||
public function jiufang_search()
|
||
{
|
||
$keyword = input('keyword', '', 'trim');
|
||
$category = input('category', '', 'trim');
|
||
$component = input('component', '', 'trim');
|
||
$page = input('page', 1, 'intval');
|
||
$pageSize = input('pageSize', 20, 'intval');
|
||
$pageSize = min(max($pageSize, 1), 50);
|
||
|
||
$where = ['switch' => 1];
|
||
$query = Db::name('jiufang')->where($where);
|
||
|
||
if (!empty($keyword)) {
|
||
if (mb_strlen($keyword) > 50) {
|
||
$this->error('关键词不能超过50个字符');
|
||
}
|
||
$query->where(function ($q) use ($keyword) {
|
||
$q->whereOr('name', 'like', '%' . $keyword . '%')
|
||
->whereOr('source', 'like', '%' . $keyword . '%')
|
||
->whereOr('usage', 'like', '%' . $keyword . '%')
|
||
->whereOr('ingredients', 'like', '%' . $keyword . '%')
|
||
->whereOr('categories', 'like', '%' . $keyword . '%')
|
||
->whereOr('components', 'like', '%' . $keyword . '%');
|
||
});
|
||
}
|
||
if (!empty($category)) {
|
||
$query->where('categories', 'like', '%' . $category . '%');
|
||
}
|
||
if (!empty($component)) {
|
||
$query->where('components', 'like', '%' . $component . '%');
|
||
}
|
||
|
||
$total = $query->count();
|
||
$list = $query->page($page, $pageSize)
|
||
->field('id,name,source,categories,components,views')
|
||
->order('views desc')
|
||
->select();
|
||
|
||
$this->success('成功', [
|
||
'total' => $total,
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'pageSize' => $pageSize,
|
||
]);
|
||
}
|
||
|
||
public function site_search()
|
||
{
|
||
$keyword = input('keyword', '', 'trim');
|
||
$type = input('type', 'tool', 'trim');
|
||
|
||
if (empty($keyword) || mb_strlen($keyword) > 50) {
|
||
$this->error('请输入有效的搜索关键词');
|
||
}
|
||
|
||
$results = [];
|
||
$kw = '%' . $keyword . '%';
|
||
|
||
if ($type === 'tool' || $type === 'all') {
|
||
$tools = Db::name('category')
|
||
->where('name|description', 'like', $kw)
|
||
->where('status', 'normal')
|
||
->field('id,name,nickname,description')
|
||
->limit(10)
|
||
->select();
|
||
foreach ($tools as $t) {
|
||
$results[] = [
|
||
'title' => $t['name'],
|
||
'desc' => $t['description'],
|
||
'url' => '/' . $t['nickname'] . '.html',
|
||
'type_label' => '工具',
|
||
];
|
||
}
|
||
}
|
||
|
||
if ($type === 'article' || $type === 'all') {
|
||
$articles = Db::name('article')
|
||
->where('title|summary', 'like', $kw)
|
||
->where('status', 'normal')
|
||
->field('id,title,summary')
|
||
->limit(10)
|
||
->select();
|
||
foreach ($articles as $a) {
|
||
$results[] = [
|
||
'title' => $a['title'],
|
||
'desc' => $a['summary'],
|
||
'url' => '/article/' . $a['id'] . '.html',
|
||
'type_label' => '文章',
|
||
];
|
||
}
|
||
}
|
||
|
||
if ($type === 'jiufang' || $type === 'all') {
|
||
$jiufang = Db::name('jiufang')
|
||
->where('name|ingredients|usage', 'like', $kw)
|
||
->where('switch', 1)
|
||
->field('id,name,categories')
|
||
->limit(10)
|
||
->select();
|
||
foreach ($jiufang as $j) {
|
||
$results[] = [
|
||
'title' => $j['name'],
|
||
'desc' => $j['categories'],
|
||
'url' => '/jiufang/' . $j['id'] . '.html',
|
||
'type_label' => '酒方',
|
||
];
|
||
}
|
||
}
|
||
|
||
if ($type === 'hanzi' || $type === 'all') {
|
||
$hanzi = Db::name('hanzi')
|
||
->where('zi|pinyin|bushou', 'like', $kw)
|
||
->where('switch', 1)
|
||
->field('id,zi,pinyin,bushou')
|
||
->limit(10)
|
||
->select();
|
||
foreach ($hanzi as $h) {
|
||
$results[] = [
|
||
'title' => $h['zi'] . ' (' . $h['pinyin'] . ')',
|
||
'desc' => '部首: ' . ($h['bushou'] ?? ''),
|
||
'url' => '/cidian/' . $h['id'] . '.html',
|
||
'type_label' => '汉字',
|
||
];
|
||
}
|
||
}
|
||
|
||
if ($type === 'poetry' || $type === 'all') {
|
||
$poetry = Db::name('poetry')
|
||
->where('name|content|author', 'like', $kw)
|
||
->where('switch', 1)
|
||
->field('id,name,author')
|
||
->limit(10)
|
||
->select();
|
||
foreach ($poetry as $p) {
|
||
$results[] = [
|
||
'title' => $p['name'],
|
||
'desc' => '作者: ' . $p['author'],
|
||
'url' => '/poetry/' . $p['id'] . '.html',
|
||
'type_label' => '诗词',
|
||
];
|
||
}
|
||
}
|
||
|
||
if ($type === 'drug' || $type === 'all') {
|
||
$drug = Db::name('drug')
|
||
->where('name|syz', 'like', $kw)
|
||
->where('switch', 1)
|
||
->field('id,name')
|
||
->limit(10)
|
||
->select();
|
||
foreach ($drug as $d) {
|
||
$results[] = [
|
||
'title' => $d['name'],
|
||
'desc' => '',
|
||
'url' => '/drug/' . $d['id'] . '.html',
|
||
'type_label' => '药品',
|
||
];
|
||
}
|
||
}
|
||
|
||
$this->success('成功', $results);
|
||
}
|
||
|
||
/**
|
||
* @name 接口调用计数
|
||
* @desc 使用txt文件记录接口调用次数,不创建新表
|
||
*/
|
||
private function _apiCount($apiName)
|
||
{
|
||
$dir = RUNTIME_PATH . 'api_count';
|
||
if (!is_dir($dir)) {
|
||
mkdir($dir, 0755, true);
|
||
}
|
||
|
||
$today = date('Y-m-d');
|
||
$file = $dir . DS . $today . '.txt';
|
||
|
||
$data = [];
|
||
if (file_exists($file)) {
|
||
$content = file_get_contents($file);
|
||
$data = json_decode($content, true) ?: [];
|
||
}
|
||
|
||
if (!isset($data[$apiName])) {
|
||
$data[$apiName] = 0;
|
||
}
|
||
$data[$apiName]++;
|
||
|
||
file_put_contents($file, json_encode($data));
|
||
|
||
return $data[$apiName];
|
||
}
|
||
|
||
/**
|
||
* @name 读取接口调用计数
|
||
* @desc 读取txt文件中的接口调用统计
|
||
*/
|
||
private function _getApiCountData($date = null)
|
||
{
|
||
$dir = RUNTIME_PATH . 'api_count';
|
||
if (!is_dir($dir)) {
|
||
return [];
|
||
}
|
||
|
||
if ($date === null) {
|
||
$date = date('Y-m-d');
|
||
}
|
||
|
||
$file = $dir . DS . $date . '.txt';
|
||
if (!file_exists($file)) {
|
||
return [];
|
||
}
|
||
|
||
$content = file_get_contents($file);
|
||
return json_decode($content, true) ?: [];
|
||
}
|
||
|
||
/**
|
||
* @name 站点统计概览
|
||
* @desc 返回站点核心统计数据(今日/本周/本月/累计)
|
||
*/
|
||
public function stats_overview()
|
||
{
|
||
$this->_apiCount('stats_overview');
|
||
|
||
$todayStart = strtotime('today');
|
||
$weekStart = strtotime('monday this week');
|
||
$monthStart = strtotime('first day of this month 00:00:00');
|
||
|
||
$data = [
|
||
'user' => $this->_timeStats('user', $todayStart, $weekStart, $monthStart),
|
||
'article' => $this->_timeStats('article', $todayStart, $weekStart, $monthStart),
|
||
'signin' => $this->_timeStats('user_signin', $todayStart, $weekStart, $monthStart),
|
||
'favorite'=> $this->_timeStats('user_favorite', $todayStart, $weekStart, $monthStart),
|
||
'note' => $this->_timeStats('user_note', $todayStart, $weekStart, $monthStart),
|
||
];
|
||
|
||
$data['tool'] = [
|
||
'total' => Db::name('category')->where('pid', '>', 0)->count(),
|
||
'normal' => Db::name('category')->where('pid', '>', 0)->where('status', 'normal')->count(),
|
||
'hidden' => Db::name('category')->where('pid', '>', 0)->where('status', 'hidden')->count(),
|
||
];
|
||
|
||
$data['attachment'] = [
|
||
'total' => Db::name('attachment')->count(),
|
||
'size' => Db::name('attachment')->sum('filesize'),
|
||
];
|
||
|
||
$contentTables = ['hanzi','poetry','drug','jiufang','cy','zc','story','wisdom','efs','couplet','riddle','brainteaser','zgjm','word'];
|
||
$contentTotal = 0;
|
||
$contentDetail = [];
|
||
foreach ($contentTables as $ct) {
|
||
try {
|
||
$c = Db::name($ct)->where('switch', 1)->count();
|
||
if ($c === 0) {
|
||
$c = Db::name($ct)->count();
|
||
}
|
||
$contentTotal += $c;
|
||
$contentDetail[$ct] = $c;
|
||
} catch (\Exception $e) {
|
||
$contentDetail[$ct] = 0;
|
||
}
|
||
}
|
||
$data['content'] = [
|
||
'total' => $contentTotal,
|
||
'detail' => $contentDetail,
|
||
];
|
||
|
||
$data['timestamp'] = time();
|
||
$data['date'] = date('Y-m-d H:i:s');
|
||
|
||
$this->success('成功', $data);
|
||
}
|
||
|
||
/**
|
||
* @name 按时间维度统计
|
||
* @desc 统计指定表今日/本周/本月/累计数据
|
||
*/
|
||
private function _timeStats($table, $todayStart, $weekStart, $monthStart)
|
||
{
|
||
$tableName = $table;
|
||
$total = Db::name($tableName)->count();
|
||
$today = Db::name($tableName)->where('createtime', '>=', $todayStart)->count();
|
||
$week = Db::name($tableName)->where('createtime', '>=', $weekStart)->count();
|
||
$month = Db::name($tableName)->where('createtime', '>=', $monthStart)->count();
|
||
|
||
$result = [
|
||
'total' => $total,
|
||
'today' => $today,
|
||
'week' => $week,
|
||
'month' => $month,
|
||
];
|
||
|
||
try {
|
||
$viewsTotal = Db::name($tableName)->sum('views');
|
||
if ($viewsTotal > 0) {
|
||
$result['views_total'] = intval($viewsTotal);
|
||
}
|
||
} catch (\Exception $e) {}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* @name 热门工具排行
|
||
* @desc 返回浏览量最高的工具列表
|
||
*/
|
||
public function stats_hot_tools()
|
||
{
|
||
$this->_apiCount('stats_hot_tools');
|
||
$limit = input('limit', 20, 'intval');
|
||
$limit = min($limit, 100);
|
||
|
||
$list = Db::name('category')
|
||
->where('pid', '>', 0)
|
||
->where('status', 'normal')
|
||
->order('views desc, id asc')
|
||
->field('id,name,nickname,views,description')
|
||
->limit($limit)
|
||
->select();
|
||
|
||
foreach ($list as &$item) {
|
||
$item['url'] = '/' . $item['nickname'] . '.html';
|
||
}
|
||
|
||
$this->success('成功', $list);
|
||
}
|
||
|
||
/**
|
||
* @name 热门文章排行
|
||
* @desc 返回浏览量最高的文章列表
|
||
*/
|
||
public function stats_hot_articles()
|
||
{
|
||
$this->_apiCount('stats_hot_articles');
|
||
$limit = input('limit', 20, 'intval');
|
||
$limit = min($limit, 100);
|
||
|
||
$list = Db::name('article')
|
||
->where('status', 'normal')
|
||
->order('views desc, id desc')
|
||
->field('id,title,views,likes,favorites,createtime')
|
||
->limit($limit)
|
||
->select();
|
||
|
||
foreach ($list as &$item) {
|
||
$item['url'] = '/article/' . $item['id'] . '.html';
|
||
$item['createtime_text'] = $item['createtime'] ? date('Y-m-d', intval($item['createtime'])) : '-';
|
||
}
|
||
|
||
$this->success('成功', $list);
|
||
}
|
||
|
||
/**
|
||
* @name 内容数据统计
|
||
* @desc 返回各内容类型的数据量和浏览量统计
|
||
*/
|
||
public function stats_content()
|
||
{
|
||
$this->_apiCount('stats_content');
|
||
$tables = [
|
||
['table' => 'hanzi', 'name' => '汉字', 'icon' => '📖'],
|
||
['table' => 'poetry', 'name' => '诗词', 'icon' => '📜'],
|
||
['table' => 'drug', 'name' => '药品', 'icon' => '💊'],
|
||
['table' => 'jiufang', 'name' => '酒方', 'icon' => '🍶'],
|
||
['table' => 'cy', 'name' => '成语', 'icon' => '🔤'],
|
||
['table' => 'zc', 'name' => '词典/组词', 'icon' => '📚'],
|
||
['table' => 'story', 'name' => '故事', 'icon' => '📖'],
|
||
['table' => 'wisdom', 'name' => '名言', 'icon' => '💡'],
|
||
['table' => 'efs', 'name' => '歇后语', 'icon' => '😄'],
|
||
['table' => 'couplet', 'name' => '对联', 'icon' => '🧧'],
|
||
['table' => 'riddle', 'name' => '谜语', 'icon' => '❓'],
|
||
['table' => 'brainteaser','name' => '脑筋急转弯', 'icon' => '🧠'],
|
||
['table' => 'zgjm', 'name' => '周公解梦', 'icon' => '🌙'],
|
||
['table' => 'word', 'name' => '词语', 'icon' => '📝'],
|
||
];
|
||
|
||
$result = [];
|
||
foreach ($tables as $t) {
|
||
try {
|
||
$count = Db::name($t['table'])->where('switch', 1)->count();
|
||
if ($count === 0) {
|
||
$count = Db::name($t['table'])->count();
|
||
}
|
||
$views = 0;
|
||
try {
|
||
$views = intval(Db::name($t['table'])->sum('views'));
|
||
} catch (\Exception $e) {}
|
||
|
||
$result[] = [
|
||
'table' => $t['table'],
|
||
'name' => $t['name'],
|
||
'icon' => $t['icon'],
|
||
'count' => $count,
|
||
'views' => $views,
|
||
];
|
||
} catch (\Exception $e) {
|
||
$result[] = [
|
||
'table' => $t['table'],
|
||
'name' => $t['name'],
|
||
'icon' => $t['icon'],
|
||
'count' => 0,
|
||
'views' => 0,
|
||
'error' => $e->getMessage(),
|
||
];
|
||
}
|
||
}
|
||
|
||
$this->success('成功', $result);
|
||
}
|
||
|
||
/**
|
||
* @name 用户活跃度统计
|
||
* @desc 返回用户签到/笔记/收藏等活跃数据
|
||
*/
|
||
public function stats_user_activity()
|
||
{
|
||
$this->_apiCount('stats_user_activity');
|
||
$todayStart = strtotime('today');
|
||
$weekStart = strtotime('monday this week');
|
||
|
||
$data = [
|
||
'signin' => [
|
||
'today' => Db::name('user_signin')->where('createtime', '>=', $todayStart)->count(),
|
||
'week' => Db::name('user_signin')->where('createtime', '>=', $weekStart)->count(),
|
||
'total' => Db::name('user_signin')->count(),
|
||
],
|
||
'note' => [
|
||
'today' => Db::name('user_note')->where('createtime', '>=', $todayStart)->count(),
|
||
'week' => Db::name('user_note')->where('createtime', '>=', $weekStart)->count(),
|
||
'total' => Db::name('user_note')->count(),
|
||
],
|
||
'favorite' => [
|
||
'today' => Db::name('user_favorite')->where('createtime', '>=', $todayStart)->count(),
|
||
'week' => Db::name('user_favorite')->where('createtime', '>=', $weekStart)->count(),
|
||
'total' => Db::name('user_favorite')->count(),
|
||
],
|
||
'article' => [
|
||
'today' => Db::name('article')->where('createtime', '>=', $todayStart)->count(),
|
||
'week' => Db::name('article')->where('createtime', '>=', $weekStart)->count(),
|
||
'total' => Db::name('article')->count(),
|
||
],
|
||
];
|
||
|
||
$data['user_total'] = Db::name('user')->count();
|
||
$data['user_active_week'] = Db::name('user')->where('logintime', '>=', $weekStart)->count();
|
||
|
||
$this->success('成功', $data);
|
||
}
|
||
|
||
/**
|
||
* @name 趋势数据统计
|
||
* @desc 返回最近7天/30天的每日数据趋势
|
||
*/
|
||
public function stats_trend()
|
||
{
|
||
$this->_apiCount('stats_trend');
|
||
$days = input('days', 7, 'intval');
|
||
$days = min($days, 30);
|
||
$days = max($days, 7);
|
||
|
||
$result = [];
|
||
for ($i = $days - 1; $i >= 0; $i--) {
|
||
$date = date('Y-m-d', strtotime("-{$i} days"));
|
||
$dayStart = strtotime($date);
|
||
$dayEnd = $dayStart + 86400;
|
||
|
||
$result[] = [
|
||
'date' => $date,
|
||
'user' => Db::name('user')->where('createtime', '>=', $dayStart)->where('createtime', '<', $dayEnd)->count(),
|
||
'article' => Db::name('article')->where('createtime', '>=', $dayStart)->where('createtime', '<', $dayEnd)->count(),
|
||
'signin' => Db::name('user_signin')->where('createtime', '>=', $dayStart)->where('createtime', '<', $dayEnd)->count(),
|
||
'note' => Db::name('user_note')->where('createtime', '>=', $dayStart)->where('createtime', '<', $dayEnd)->count(),
|
||
'favorite'=> Db::name('user_favorite')->where('createtime', '>=', $dayStart)->where('createtime', '<', $dayEnd)->count(),
|
||
];
|
||
}
|
||
|
||
$this->success('成功', $result);
|
||
}
|
||
|
||
/**
|
||
* @name 接口调用统计
|
||
* @desc 返回接口调用次数统计(基于txt文件计数)
|
||
*/
|
||
public function stats_api_usage()
|
||
{
|
||
$this->_apiCount('stats_api_usage');
|
||
|
||
$days = input('days', 7, 'intval');
|
||
$days = min($days, 30);
|
||
$days = max($days, 1);
|
||
|
||
$result = [];
|
||
$allApis = [];
|
||
|
||
for ($i = $days - 1; $i >= 0; $i--) {
|
||
$date = date('Y-m-d', strtotime("-{$i} days"));
|
||
$data = $this->_getApiCountData($date);
|
||
$dayTotal = 0;
|
||
foreach ($data as $api => $count) {
|
||
if (!in_array($api, $allApis)) {
|
||
$allApis[] = $api;
|
||
}
|
||
$dayTotal += $count;
|
||
}
|
||
$result[] = [
|
||
'date' => $date,
|
||
'total' => $dayTotal,
|
||
'apis' => $data,
|
||
];
|
||
}
|
||
|
||
$apiSummary = [];
|
||
foreach ($allApis as $api) {
|
||
$total = 0;
|
||
foreach ($result as $day) {
|
||
$total += isset($day['apis'][$api]) ? $day['apis'][$api] : 0;
|
||
}
|
||
$apiSummary[] = [
|
||
'api' => $api,
|
||
'total' => $total,
|
||
];
|
||
}
|
||
|
||
usort($apiSummary, function($a, $b) {
|
||
return $b['total'] - $a['total'];
|
||
});
|
||
|
||
$grandTotal = 0;
|
||
foreach ($result as $day) {
|
||
$grandTotal += $day['total'];
|
||
}
|
||
|
||
$this->success('成功', [
|
||
'daily' => $result,
|
||
'api_summary'=> $apiSummary,
|
||
'grand_total'=> $grandTotal,
|
||
'api_count' => count($allApis),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* @name 实时请求统计
|
||
* @desc 返回累计请求总量和每秒请求数,支持app端轮询
|
||
*/
|
||
public function stats_realtime()
|
||
{
|
||
$dir = RUNTIME_PATH . 'api_count';
|
||
if (!is_dir($dir)) {
|
||
$this->success('成功', [
|
||
'total_requests' => 0,
|
||
'today_requests' => 0,
|
||
'qps' => 0,
|
||
'apis' => [],
|
||
'timestamp' => time(),
|
||
]);
|
||
}
|
||
|
||
$grandTotal = 0;
|
||
$files = glob($dir . DS . '*.txt');
|
||
$todayData = [];
|
||
foreach ($files as $f) {
|
||
$content = file_get_contents($f);
|
||
$data = json_decode($content, true) ?: [];
|
||
foreach ($data as $api => $count) {
|
||
$grandTotal += $count;
|
||
}
|
||
$basename = basename($f, '.txt');
|
||
if ($basename === date('Y-m-d')) {
|
||
$todayData = $data;
|
||
}
|
||
}
|
||
|
||
$todayTotal = 0;
|
||
foreach ($todayData as $count) {
|
||
$todayTotal += $count;
|
||
}
|
||
|
||
$qpsFile = $dir . DS . '_qps.json';
|
||
$qps = 0;
|
||
$now = time();
|
||
|
||
if (file_exists($qpsFile)) {
|
||
$prev = json_decode(file_get_contents($qpsFile), true) ?: [];
|
||
if (isset($prev['last_time']) && ($now - $prev['last_time']) > 0) {
|
||
$diff = $todayTotal - ($prev['last_count'] ?? 0);
|
||
$elapsed = $now - $prev['last_time'];
|
||
if ($elapsed > 0 && $diff >= 0) {
|
||
$qps = round($diff / $elapsed, 2);
|
||
}
|
||
}
|
||
}
|
||
|
||
$qpsData = ['last_time' => $now, 'last_count' => $todayTotal, 'qps' => $qps];
|
||
file_put_contents($qpsFile, json_encode($qpsData));
|
||
|
||
$apis = [];
|
||
foreach ($todayData as $api => $count) {
|
||
$apis[] = ['name' => $api, 'count' => $count];
|
||
}
|
||
usort($apis, function($a, $b) { return $b['count'] - $a['count']; });
|
||
|
||
$this->success('成功', [
|
||
'total_requests' => $grandTotal,
|
||
'today_requests' => $todayTotal,
|
||
'qps' => $qps,
|
||
'apis' => $apis,
|
||
'timestamp' => time(),
|
||
'date' => date('Y-m-d H:i:s'),
|
||
]);
|
||
}
|
||
} |