作者 刘锟

Merge remote-tracking branch 'origin/master' into akun

... ... @@ -95,7 +95,7 @@ class RemainDay extends Command
if($item['type'] != Project::TYPE_THREE){
$pause_days = $item['pause_days'] + 1;
}
// $this->project->edit(['pause_days'=>$pause_days],['id'=>$item['id']]);
$this->project->edit(['pause_days'=>$pause_days],['id'=>$item['id']]);
continue;
}
//白帽版本的系统
... ... @@ -110,11 +110,11 @@ class RemainDay extends Command
$seo_remain_day = 0;
}
if($deploy_build['plan'] == 0 && $seo_remain_day == 0 && $deploy_build['seo_service_duration'] != 0){//只有白帽版本的项目且剩余服务时常未0,放入未续费中
// $this->project->edit(['seo_remain_day'=>$seo_remain_day,'extend_type'=>Project::TYPE_FIVE],['id'=>$item['id']]);
$this->project->edit(['seo_remain_day'=>$seo_remain_day,'extend_type'=>Project::TYPE_FIVE],['id'=>$item['id']]);
continue;
}
//同时包括白帽版本+默认版本的项目
// $this->project->edit(['seo_remain_day'=>$seo_remain_day],['id'=>$item['id']]);
$this->project->edit(['seo_remain_day'=>$seo_remain_day],['id'=>$item['id']]);
}
//默认版本计算剩余服务时常
if($item['type'] == Project::TYPE_TWO || $item['type'] == Project::TYPE_FOUR){
... ... @@ -144,7 +144,7 @@ class RemainDay extends Command
$remain_day = 0;
$extend_type = Project::TYPE_FIVE;
}
// $this->project->edit(['remain_day'=>$remain_day,'extend_type'=>$extend_type],['id'=>$item['id']]);
$this->project->edit(['remain_day'=>$remain_day,'extend_type'=>$extend_type],['id'=>$item['id']]);
echo 'end->项目id:' . $item['id'] . '执行时间:'. date('Y-m-d H:i:s') . PHP_EOL;
}
return true;
... ...
... ... @@ -14,7 +14,7 @@ use App\Models\Domain\DomainInfo;
use App\Models\GoogleSearch\GoogleSearch;
use App\Models\GoogleSearch\GoogleSearchDetail;
use App\Models\Project\Project;
use App\Services\GoogleSearchService;
use App\Services\RapIdApIService;
use App\Services\ProjectServer;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
... ... @@ -48,7 +48,7 @@ class GoogleSearchKeyword extends Command
public function __construct()
{
$this->googleService = new GoogleSearchService();
$this->googleService = new RapIdApIService();
$this->searchModel = new GoogleSearch();
$this->detailModel = new GoogleSearchDetail();
parent::__construct();
... ...
... ... @@ -13,7 +13,9 @@ use App\Helper\Arr;
use App\Helper\Translate;
use App\Models\Ai\AiBlog;
use App\Models\Blog\Blog;
use App\Models\Com\WordCountry;
use App\Models\CustomModule\CustomModuleContent;
use App\Models\GoogleSearch\GoogleCodeCountry;
use App\Models\Product\CategoryRelated;
use App\Models\Product\Keyword;
use App\Models\Product\Product;
... ... @@ -64,7 +66,7 @@ class UpdateRoute extends Command
*/
public function handle()
{
$this->updateProjectOp();
$this->insertData();
}
/**
* @remark :
... ... @@ -543,9 +545,264 @@ class UpdateRoute extends Command
* @method :post
* @time :2025/3/14 14:39
*/
public function updateSeo(){
$contentModel = new CustomModuleContent();
$contentModel->edit(['seo_title'=>''],['created_at'=>['<=','2024-11-25 00:00:00']]);
public function insertData(){
$countries = [
'AFG' => 'Afghanistan',
'ALB' => 'Albania',
'DZA' => 'Algeria',
'AND' => 'Andorra',
'AGO' => 'Angola',
'ATG' => 'Antigua and Barbuda',
'ARG' => 'Argentina',
'ARM' => 'Armenia',
'AUS' => 'Australia',
'AUT' => 'Austria',
'AZE' => 'Azerbaijan',
'BHS' => 'Bahamas',
'BHR' => 'Bahrain',
'BGD' => 'Bangladesh',
'BRB' => 'Barbados',
'BLR' => 'Belarus',
'BEL' => 'Belgium',
'BLZ' => 'Belize',
'BEN' => 'Benin',
'BTN' => 'Bhutan',
'BOL' => 'Bolivia',
'BES' => 'Bonaire, Sint Eustatius and Saba',
'BIH' => 'Bosnia and Herzegovina',
'BWA' => 'Botswana',
'BVT' => 'Bouvet Island',
'BRA' => 'Brazil',
'IOT' => 'British Indian Ocean Territory',
'BRN' => 'Brunei Darussalam',
'BGR' => 'Bulgaria',
'BFA' => 'Burkina Faso',
'BDI' => 'Burundi',
'CPV' => 'Cabo Verde',
'KHM' => 'Cambodia',
'CMR' => 'Cameroon',
'CAN' => 'Canada',
'CYM' => 'Cayman Islands',
'CAF' => 'Central African Republic',
'TCD' => 'Chad',
'CHL' => 'Chile',
'CHN' => 'China',
'CXR' => 'Christmas Island',
'CCK' => 'Cocos (Keeling) Islands',
'COL' => 'Colombia',
'COM' => 'Comoros',
'COD' => 'Congo (Democratic Republic of the)',
'COG' => 'Congo',
'COK' => 'Cook Islands',
'CRI' => 'Costa Rica',
'CIV' => 'Côte d\'Ivoire',
'HRV' => 'Croatia',
'CUB' => 'Cuba',
'CUW' => 'Curaçao',
'CYP' => 'Cyprus',
'CZE' => 'Czech Republic',
'DNK' => 'Denmark',
'DJI' => 'Djibouti',
'DMA' => 'Dominica',
'DOM' => 'Dominican Republic',
'ECU' => 'Ecuador',
'EGY' => 'Egypt',
'SLV' => 'El Salvador',
'GNQ' => 'Equatorial Guinea',
'ERI' => 'Eritrea',
'EST' => 'Estonia',
'SWZ' => 'Eswatini',
'ETH' => 'Ethiopia',
'FLK' => 'Falkland Islands (Malvinas)',
'FRO' => 'Faroe Islands',
'FJI' => 'Fiji',
'FIN' => 'Finland',
'FRA' => 'France',
'GUF' => 'French Guiana',
'PYF' => 'French Polynesia',
'ATF' => 'French Southern Territories',
'GAB' => 'Gabon',
'GMB' => 'Gambia',
'GEO' => 'Georgia',
'DEU' => 'Germany',
'GHA' => 'Ghana',
'GIB' => 'Gibraltar',
'GRC' => 'Greece',
'GRL' => 'Greenland',
'GRD' => 'Grenada',
'GLP' => 'Guadeloupe',
'GUM' => 'Guam',
'GTM' => 'Guatemala',
'GGY' => 'Guernsey',
'GIN' => 'Guinea',
'GNB' => 'Guinea-Bissau',
'GUY' => 'Guyana',
'HTI' => 'Haiti',
'HMD' => 'Heard Island and McDonald Islands',
'VAT' => 'Holy See',
'HND' => 'Honduras',
'HKG' => 'Hong Kong',
'HUN' => 'Hungary',
'ISL' => 'Iceland',
'IND' => 'India',
'IDN' => 'Indonesia',
'IRN' => 'Iran (Islamic Republic of)',
'IRQ' => 'Iraq',
'IRL' => 'Ireland',
'IMN' => 'Isle of Man',
'ISR' => 'Israel',
'ITA' => 'Italy',
'JAM' => 'Jamaica',
'JPN' => 'Japan',
'JEY' => 'Jersey',
'JOR' => 'Jordan',
'KAZ' => 'Kazakhstan',
'KEN' => 'Kenya',
'KIR' => 'Kiribati',
'KOR' => 'Korea (Republic of)',
'KWT' => 'Kuwait',
'KGZ' => 'Kyrgyzstan',
'LAO' => 'Lao People\'s Democratic Republic',
'LVA' => 'Latvia',
'LBN' => 'Lebanon',
'LSO' => 'Lesotho',
'LBR' => 'Liberia',
'LBY' => 'Libya',
'LIE' => 'Liechtenstein',
'LTU' => 'Lithuania',
'LUX' => 'Luxembourg',
'MAC' => 'Macao',
'MDG' => 'Madagascar',
'MWI' => 'Malawi',
'MYS' => 'Malaysia',
'MDV' => 'Maldives',
'MLI' => 'Mali',
'MLT' => 'Malta',
'MHL' => 'Marshall Islands',
'MTQ' => 'Martinique',
'MRT' => 'Mauritania',
'MUS' => 'Mauritius',
'MYT' => 'Mayotte',
'MEX' => 'Mexico',
'FSM' => 'Micronesia (Federated States of)',
'MDA' => 'Moldova (Republic of)',
'MCO' => 'Monaco',
'MNG' => 'Mongolia',
'MNE' => 'Montenegro',
'MSR' => 'Montserrat',
'MAR' => 'Morocco',
'MOZ' => 'Mozambique',
'MMR' => 'Myanmar',
'NAM' => 'Namibia',
'NRU' => 'Nauru',
'NPL' => 'Nepal',
'NLD' => 'Netherlands',
'NCL' => 'New Caledonia',
'NZL' => 'New Zealand',
'NIC' => 'Nicaragua',
'NER' => 'Niger',
'NGA' => 'Nigeria',
'NIU' => 'Niue',
'NFK' => 'Norfolk Island',
'MNP' => 'Northern Mariana Islands',
'NOR' => 'Norway',
'OMN' => 'Oman',
'PAK' => 'Pakistan',
'PLW' => 'Palau',
'PSE' => 'Palestine, State of',
'PAN' => 'Panama',
'PNG' => 'Papua New Guinea',
'PRY' => 'Paraguay',
'PER' => 'Peru',
'PHL' => 'Philippines',
'PCN' => 'Pitcairn',
'POL' => 'Poland',
'PRT' => 'Portugal',
'PRI' => 'Puerto Rico',
'QAT' => 'Qatar',
'REU' => 'Réunion',
'ROU' => 'Romania',
'RUS' => 'Russian Federation',
'RWA' => 'Rwanda',
'BLM' => 'Saint Barthélemy',
'SHN' => 'Saint Helena, Ascension and Tristan da Cunha',
'KNA' => 'Saint Kitts and Nevis',
'LCA' => 'Saint Lucia',
'MAF' => 'Saint Martin (French part)',
'SPM' => 'Saint Pierre and Miquelon',
'VCT' => 'Saint Vincent and the Grenadines',
'WSM' => 'Samoa',
'SMR' => 'San Marino',
'STP' => 'Sao Tome and Principe',
'SAU' => 'Saudi Arabia',
'SEN' => 'Senegal',
'SRB' => 'Serbia',
'SYC' => 'Seychelles',
'SLE' => 'Sierra Leone',
'SGP' => 'Singapore',
'SXM' => 'Sint Maarten (Dutch part)',
'SVK' => 'Slovakia',
'SVN' => 'Slovenia',
'SLB' => 'Solomon Islands',
'SOM' => 'Somalia',
'ZAF' => 'South Africa',
'SGS' => 'South Georgia and the South Sandwich Islands',
'SSD' => 'South Sudan',
'ESP' => 'Spain',
'LKA' => 'Sri Lanka',
'SDN' => 'Sudan',
'SUR' => 'Suriname',
'SJM' => 'Svalbard and Jan Mayen',
'SWE' => 'Sweden',
'CHE' => 'Switzerland',
'SYR' => 'Syrian Arab Republic',
'TWN' => 'Taiwan, Province of China',
'TJK' => 'Tajikistan',
'TZA' => 'Tanzania, United Republic of',
'THA' => 'Thailand',
'TLS' => 'Timor-Leste',
'TGO' => 'Togo',
'TKL' => 'Tokelau',
'TON' => 'Tonga',
'TTO' => 'Trinidad and Tobago',
'TUN' => 'Tunisia',
'TUR' => 'Turkey',
'TKM' => 'Turkmenistan',
'TCA' => 'Turks and Caicos Islands',
'TUV' => 'Tuvalu',
'UGA' => 'Uganda',
'UKR' => 'Ukraine',
'ARE' => 'United Arab Emirates',
'GBR' => 'United Kingdom',
'USA' => 'United States',
'URY' => 'Uruguay',
'UZB' => 'Uzbekistan',
'VUT' => 'Vanuatu',
'VEN' => 'Venezuela (Bolivarian Republic of)',
'VNM' => 'Viet Nam',
'WLF' => 'Wallis and Futuna',
'ESH' => 'Western Sahara',
'YEM' => 'Yemen',
'ZMB' => 'Zambia',
'ZWE' => 'Zimbabwe',
];
$codeCountryModel = new GoogleCodeCountry();
$data = [];
foreach ($countries as $key =>$val){
$wordModel = new WordCountry();
$info = $wordModel->read(['iso3'=>$key]);
if($info === false){
$zh = Translate::tran($val,'zh');
}else{
$zh = $info['chinese_name'];
}
$data[] = [
'code'=>$key,
'en_country'=>$val,
'zh_country'=>$zh,
];
}
$codeCountryModel->insertAll($data);
return true;
}
}
... ...
... ... @@ -106,6 +106,7 @@ class SendKeyword extends Command
unset($item['main_lang_id']);
unset($item['is_auto_keywords']);
$item['day'] = $day;
$item['apino'] = $item['api_no'];
$this->sendNotice($item);
}
}
... ...
... ... @@ -1226,7 +1226,7 @@ public static function getCountryNameByAlpha3($alpha3) {
if (array_key_exists($alpha3, $countries)) {
return $countries[$alpha3];
} else {
return false;
return '未知国家';
}
}
... ...
<?php
namespace App\Helper;
/**
* api结果处理
* @author:dc
* @time 2023/12/16 14:56
* Class Resource
* @package GlobalSo\Tool\Gpt
*/
abstract class Resource {
/**
* 函数
* @var array
*/
protected $func = [];
/**
* 是否是调试模式
* @var bool
*/
public static $debugInfo = false;
/**
* 获取内容body所有内容
* @return string
*/
abstract public function getBody(): string;
/**
* 获取数据的状态
* @return int
*/
abstract public function getCode(): int;
/**
* 验证code 默认200
* @param int $code
* @return bool
* @time 2023/12/16 16:45
*/
public function checkCode(int $code = 200):bool {
return $this->getCode() === $code;
}
/**
* 获取内容 数据
* @return mixed
* @author:dc
* @time 2024/1/2 15:56
*/
abstract public function getData();
/**
* 获取错误消息
* @return string
*/
abstract public function getMessage(): string;
/**
* 是否是函数
* @return bool
* @author:dc
* @time 2024/1/10 12:35
*/
public function isFun(){
if($this->getBodyFunc()){
return true;
}
return false;
}
/**
* @return array|mixed
* @author:dc
* @time 2024/4/9 15:55
*/
public function getBodyFunc(){
$json = @json_decode($this->getBody(),1);
if(is_array($json) && isset($json['tool_calls'])){
return $json['tool_calls'];
}
return [];
}
/**
* 这个是提交了多少token
* @return int
* @author:dc
* @time 2023/12/18 13:37
*/
public function getPromptToken():int{
// 这个是老版本
if(isset($this->getUsage()['prompt_tokens'])){
return (int) ($this->getUsage()['prompt_tokens']??0);
}
// 这个是新版本
$num = 0;
foreach ($this->getUsage() as $item){
$num += (int) ($item['prompt_tokens']??0);
}
return $num;
}
/**
* 这个是吐出了多少token
* @return int
* @author:dc
* @time 2023/12/18 13:37
*/
public function getCompletionToken():int{
// 这个是老版本
if(isset($this->getUsage()['completion_tokens'])){
return (int) ($this->getUsage()['completion_tokens']??0);
}
// 这个是新版本
$num = 0;
foreach ($this->getUsage() as $item){
$num += (int) ($item['completion_tokens']??0);
}
return $num;
}
/**
* 获取函数
* @return array|mixed
*/
public function getFunc($call=null,...$params)
{
if(!$this->func){
$this->func = $this->getBodyFunc();
}
// {"code":200,"func":{"name":"taocan","arguments":{"attribute":"\u7528\u91cf","usetime":"\u4eca\u5929"}}}
$function = $this->func;
if(!empty($this->func) && is_array($this->func)) {
if (!isset($this->func['name'])) {
$this->func = [$function[0]];
}
}
$result = $this->getFuncAll($call,...$params);
$this->func = $function;
return array_values($result)[0]??null;
}
/**
* 获取所有的函数体
* @param null $call
* @param mixed ...$params
* @return array|false|mixed
* @author:dc
* @time 2024/3/11 14:32
*/
public function getFuncAll($call=null,...$params)
{
if(!$this->func){
$this->func = $this->getBodyFunc();
}
// {"code":200,"func":{"name":"taocan","arguments":{"attribute":"\u7528\u91cf","usetime":"\u4eca\u5929"}}}
$function = [];
if(!empty($this->func) && is_array($this->func)){
$function = $this->func;
}
if($call){
if($function){
// 是否是老版本
if(!empty($function['name'])){
// 整理参数
return [$function['name']=> $this->callFunc(
$call,
$function['name'],
$params,
$function['arguments']??[]
)];
}else{
// 循环 函数
$result = [];
foreach ($function as $func){
$result[$func['name']] = $this->callFunc(
$call,
$func['name'],
$params,
$func['arguments']??[]
);
}
return $result;
}
}
return [];
}
return $function;
}
/**
* call 回调
* @param $call
* @param $funcName
* @param $params
* @return false|mixed
* @author:dc
* @time 2024/3/11 11:35
*/
private function callFunc($call,$funcName,$params,$attr) {
$params[] = $attr;
// 匿名函数
if($call instanceof \Closure){
return $call($funcName, ...$params);
}
// 类名称
elseif ($call){
// 定义了类
if(is_object($call) || class_exists($call)){
// 掉用类
return call_user_func(
[$call,$funcName]
,...$params
);
}else
return call_user_func(
[$call,$funcName]
,...$params
);
}
}
}
... ...
<?php
namespace App\Helper;
/**
* 流输出
* @author:dc
* @time 2024/1/2 14:46
* Class Stream
* @package GlobalSo\Tool\Gpt\Resource
*/
class Stream extends Resource{
/**
* body内容
* @var string
*/
private $body = '';
/**
* 流输出的文本
* @var string
*/
private $text = '';
/**
* http 状态
* @var int
*/
private $status = 200;
/**
* @var \Psr\Http\Message\StreamInterface
*/
private $stream;
/**
* @var array 使用了多少token
*/
private $usage = [];
/**
* Resource constructor.
* @param \Psr\Http\Message\StreamInterface|array $response
*/
public function __construct($response)
{
if($response instanceof \Psr\Http\Message\StreamInterface){
$this->stream = $response;
}
// 数组,带上下文
elseif(is_array($response)){
$this->stream = false;
// 回答的文本
$this->text = end($response);
// 计算token
$this->usage = [
[
'model'=>'',
]
];
}
}
/**
* 最后一行
* @var array
*/
private $endLine = [];
/**
* 获取流输出内容
* @return null
* @author:dc
* @time 2024/1/2 13:57
*/
public function getStreamContent(\Closure $call)
{
// 文本
if($this->stream===false){
$this->body = $this->text;
$call($this->text);
}
// 流输出
else{
while (!$this->stream->eof()) {
// 获取一行数据
$line = $this->getStreamContentLine();
// 必须要有数据
if($line){
// 解析成数组
$arr = @json_decode($line,true);
// 必须是一个数组
if(is_array($arr)){
// 是否是函数
if(!empty($arr['func'])){
$this->func = $arr['func'] ? : ($arr['tool_calls']??[]);
continue;
}
// 这里是新版本
// 文本
if(isset($arr['text'])){
// 拼接
$this->text .= $arr['text'];
// 调用
$call($arr['text']);
}
// 到了最后一行
if (isset($arr['usage'])){
$this->usage = $arr['usage'];
$this->endLine = $arr;
}
}else{
// 拼接
$this->text .= $line;
// 这里兼容下老版本
$call($line);
}
}
}
// 兼容老版本 老版本没办法获取 实际使用了多少token
if(!$this->usage){
$this->usage = [
[
'model'=>'',
]
];
}
}
}
/**
* 流 读取一行
* @return string
* @author:dc
* @time 2024/1/2 14:16
*/
private function getStreamContentLine(){
$text = '';
while (!$this->stream->eof()){
// 读取一个字符串
$t = $this->stream->read(1);
$this->body .= $t;
if($t === "\n"){
break;
}
// 结束了
if(ord($t)==1){
break;
}
$text .= $t;
}
return $text;
}
/**
* 流输出的所有内容
* @return string
*/
public function getBody(): string
{
return $this->body;
}
/**
* @return int
*/
public function getCode(): int
{
return 200;
}
/**
* 这个是文本内容,就是回答的内容
* @return array|string
* @author:dc
* @time 2024/1/2 14:57
*/
public function getData()
{
return $this->text;
}
/**
* @return string
*/
public function getMessage(): string
{
return '';
}
/**
* @return array
* @author:dc
* @time 2024/1/2 14:57
*/
public function getUsage(): array
{
return $this->usage;
}
/**
* 是否已经输出过头部了
* @var bool
*/
protected static $isHeader = false;
/**
* 是否是sse输出
* @var bool
*/
public static $echoSse = false;
/**
* 设置头部
* @param false $sse
* @author:dc
* @time 2024/5/31 15:02
*/
public static function setStreamHeader(array $header=[]){
// 默认配置的 头信息 输出一次即可
if(!self::$isHeader){
// 流输出 必须的 头信息
if(self::$echoSse) header("Content-Type:event-stream;Charset=UTF-8;");//event-stream 开启这个数据必须是规定格式
header("cache-control:no-cache;"); // 告诉浏览器不要进行数据缓存
header('X-Accel-Buffering: no'); // 关键是加了这一行。告诉浏览器不进行输出的缓冲
header('Access-Control-Expose-Headers: Content-Disposition, Content-Length, X-Content-Range, X-Duration');
header('Content-Type: application/json'); // json数据头
header('Access-Control-Allow-Origin:*'); // 这个是 跨域
self::$isHeader = true;
}
// 输出其他header
foreach ($header as $head){
header($head);
}
}
/**
* 其他地方调用,在ai返回前后都可以调用这个
* @param $data
* @param string $type 数据类型
* @author:dc
* @time 2024/5/31 15:05
*/
public static function echo_flush($data,string $type='text'){
self::setStreamHeader();
echo self::$echoSse ? en_sse_data($data,$type) : $data;
ob_flush();
flush();
}
/**
* 输出 信息到前端
* @author:dc
* @time 2024/5/31 10:16
*/
public function echo(){
// 如果用户断开,继续脚本的运行
ignore_user_abort(1);
set_time_limit(400);
// // 先把之前的内容 也发送到浏览器
// @ob_implicit_flush(); // 开启隐式刷新 使用 echo函数时会立即发送到浏览器 开启后就不需要flush调用了
// 输出内容
$this->getStreamContent(function ($text) {
self::echo_flush($text);
});
if(self::$debugInfo){
self::echo_flush($this->endLine['debug']??[],'debug');
}
}
}
... ...
... ... @@ -215,7 +215,7 @@ class CNoticeController extends BaseController
$project_id = $this->user['project_id'];
$type = intval($request->input('type', 1));
$route = intval($request->input('page', 1));
if(in_array($route,[4,6])){
if($type == 2 && in_array($route,[4,6])){
$this->fail('聚合页翻译请联系管理员');
}
$url = $request->input('url', []);
... ...
... ... @@ -51,14 +51,17 @@ class GoogleKeywordInsightController extends BaseController
* @time :2025/4/1 9:12
*/
public function getOptimizeList(){
$this->request->validate([
'field' => 'required'
],[
'field.required' => 'field不能为空',
]);
$projectKeywordModel = new ProjectKeyword();
$info = $projectKeywordModel->read(['project_id'=>$this->user['project_id']],['main_keyword','customer_keywords']);
$info = $projectKeywordModel->read(['project_id'=>$this->user['project_id']],[$this->param['field']]);
if($info === false){
$this->response('success');
}
$main_keyword = explode("\r\n", $info['main_keyword']);
$customer_keywords = explode("\r\n", $info['customer_keywords']);
$array = array_merge($main_keyword, $customer_keywords);
$array = explode("\r\n", $info[$this->param['field']]);
$detailModel = new GoogleKeywordInsightDetail();
$resultData = [];
if(!empty($array)){
... ...
... ... @@ -10,11 +10,14 @@
namespace App\Http\Controllers\Bside\GoogleKeyword;
use App\Enums\Common\Code;
use App\Helper\Country;
use App\Helper\Translate;
use App\Http\Controllers\Bside\BaseController;
use App\Models\Com\NoticeLog;
use App\Models\GoogleSearch\GoogleCodeCountry;
use App\Models\GoogleSearch\GoogleSearch;
use App\Models\GoogleSearch\GoogleSearchDetail;
use App\Services\GoogleSearchService;
use App\Services\RapIdApIService;
class GoogleSearchController extends BaseController
{
... ... @@ -33,8 +36,25 @@ class GoogleSearchController extends BaseController
]);
//查询详情数据
$searchDetailModel = new GoogleSearchDetail();
$this->map['project_id']= $this->user['project_id'];
$data = $searchDetailModel->lists($this->map,$this->page,$this->row);
$this->map['project_id']= 711;
$data = $searchDetailModel->lists($this->map,$this->page,$this->row,'impressions');
if(!empty($data)){
if($this->param['type'] == 'country'){
$codeCountryModel = new GoogleCodeCountry();
foreach ($data['list'] as $key => $val){
$val['zh_country'] = $codeCountryModel->getCodeCountry($val['keys']);
$val['click_rate'] = number_format($val['click_rate'] * 100, 2); // 保留 2 位小数
$val['impressions_rate'] = number_format($val['impressions_rate'] * 100, 2);
$data['list'][$key] = $val;
}
}else{
foreach ($data['list'] as $key => $val){
$val['click_rate'] = number_format($val['click_rate'] * 100, 2); // 保留 2 位小数
$val['impressions_rate'] = number_format($val['impressions_rate'] * 100, 2);
$data['list'][$key] = $val;
}
}
}
$this->response('success',Code::SUCCESS,$data);
}
}
... ...
... ... @@ -12,11 +12,42 @@ namespace App\Http\Controllers\Bside\Gpt;
use App\Enums\Common\Code;
use App\Http\Controllers\Bside\BaseController;
use App\Http\Logic\Bside\Gpt\ChatLogic;
use App\Models\Gpt\Chat;
use App\Models\Gpt\ChatItem;
class ChatController extends BaseController
{
/**
* @remark :发送消息
* @remark :获取消息列表
* @name :list
* @author :lyh
* @method :post
* @time :2025/4/2 15:51
*/
public function list(Chat $chat){
$list = $chat->lists(['user_id'=>$this->user['id'],'status'=>1],$this->page,$this->row);
$this->response('success',Code::SUCCESS,$list);
}
/**
* @remark :获取所有子消息
* @name :itemList
* @author :lyh
* @method :post
* @time :2025/4/2 15:55
*/
public function itemList(ChatItem $chatItem){
$this->request->validate([
'chat_id'=>['required'],
],[
'chat_id.required' => 'chat_id不能为空',
]);
$list = $chatItem->list(['user_id'=>$this->user['id'],'chat_id'=>$this->map['chat_id']],'id',['*'],'asc');
$this->response('success',Code::SUCCESS,$list);
}
/**
* @remark :发送消息(流返回)
* @name :sendMessage
* @author :lyh
* @method :post
... ... @@ -28,7 +59,23 @@ class ChatController extends BaseController
],[
'message.required' => '消息内容不能为空',
]);
$data = $logic->sendMessage();
return $logic->sendMessage();
}
/**
* @remark :删除消息
* @name :del
* @author :lyh
* @method :post
* @time :2025/4/2 15:54
*/
public function del(Chat $chat){
$this->request->validate([
'id'=>['required'],
],[
'id.required' => 'id不能为空',
]);
$data = $chat->edit(['status'=>0],['id'=>$this->param['id']]);
$this->response('success',Code::SUCCESS,$data);
}
}
... ...
... ... @@ -137,12 +137,8 @@ class SuppliersController extends BaseController
if(isset($this->param['position'])){
$param['position'] = $this->param['position'];
}
try {
$res = $this->_action($api_url,$action_name,$param);
$this->response('success',Code::SUCCESS,$res);
}catch (\Exception $e){
$this->fail('请求失败,请联系管理员');
}
$res = $this->_action($api_url,$action_name,$param);
$this->response('success',Code::SUCCESS,$res);
}
/**
... ...
... ... @@ -28,7 +28,7 @@ use App\Models\Project\Project;
use App\Models\Project\ProjectAiSetting;
use App\Models\RouteMap\RouteMap;
use App\Services\AiBlogService;
use App\Services\GoogleSearchService;
use App\Services\RapIdApIService;
use App\Services\ProjectServer;
use Illuminate\Support\Facades\DB;
... ...
... ... @@ -13,7 +13,7 @@ use App\Helper\Translate;
use App\Http\Logic\Bside\BaseLogic;
use App\Models\GoogleKeywordInsight\GoogleKeywordInsight;
use App\Models\GoogleKeywordInsight\GoogleKeywordInsightDetail;
use App\Services\GoogleSearchService;
use App\Services\RapIdApIService;
use Illuminate\Support\Facades\DB;
class GoogleKeywordInsightLogic extends BaseLogic
... ... @@ -37,7 +37,7 @@ class GoogleKeywordInsightLogic extends BaseLogic
public function getGoogleInsight(){
$data = $this->model->read(['search'=>$this->param['keyword']],['id']);
if($data === false){
$this->service = new GoogleSearchService();
$this->service = new RapIdApIService();
$data = $this->service->requestUrl($this->param['keyword']);
if(!empty($data)){
DB::beginTransaction();
... ... @@ -64,7 +64,7 @@ class GoogleKeywordInsightLogic extends BaseLogic
* @time :2025/3/25 14:36
*/
public function getGoogleInsightDetail(){
$this->service = new GoogleSearchService();
$this->service = new RapIdApIService();
$data = $this->service->requestUrl($this->param['keyword']);
if(!empty($data)){
DB::beginTransaction();
... ...
... ... @@ -9,15 +9,20 @@
namespace App\Http\Logic\Bside\Gpt;
use App\Helper\Stream;
use App\Http\Logic\Bside\BaseLogic;
use App\Models\Gpt\Chat;
use App\Models\Gpt\ChatItem;
use App\Services\GptService;
class ChatLogic extends BaseLogic
{
public function __construct()
{
parent::__construct();
$this->param = $this->requestAll;
$this->model = new Chat();
$this->itemModel = new ChatItem();
}
/**
... ... @@ -27,19 +32,77 @@ class ChatLogic extends BaseLogic
* @method :post
* @time :2025/4/2 10:01
*/
public function sendMessage(){
if(isset($this->param['chat_id'])){
$chatInfo = $this->model->read(['id'=>$this->param['chat_id']]);
if($chatInfo === false){
$id = $this->saveChat($this->param['message']);
public function sendMessage()
{
$gptService = new GptService();
$message = [];
if (isset($this->param['chat_id'])) {
$chatInfo = $this->model->read(['id' => $this->param['chat_id']]);
if ($chatInfo !== false) {
$this->saveChatItem($chatInfo['id'], $this->param['message']);
// 获取最近 2 条对话记录
$list = $this->itemModel->list(['chat_id' => $chatInfo['id']], 'id', ['*'], 'desc', 2);
$message[] = ['role' => 'system', 'content' => "You are now the marketing customer service of 深圳创贸集团"];
foreach ($list as $val) {
if ($val['is_reply'] == 2) {
$message[] = ['role' => 'user', 'content' => $val['content']];
} else {
$message[] = ['role' => 'assistant', 'content' => $val['content']];
}
}
$message[] = ['role' => 'user', 'content' => $this->param['message']];
$chatId = $chatInfo['id'];
}else{
$id = $chatInfo['id'];
$chatId = $this->saveChat($this->param['message']);
$this->saveChatItem($chatId, $this->param['message']);
$message = [
['role' => 'system', 'content' => "You are now the marketing customer service of 深圳创贸集团"],
['role' => 'user', 'content' => $this->param['message']],
];
}
}else{
$id = $this->saveChat($this->param['message']);
} else {
$chatId = $this->saveChat($this->param['message']);
$this->saveChatItem($chatId, $this->param['message']);
$message = [
['role' => 'system', 'content' => "You are now the marketing customer service of 深圳创贸集团"],
['role' => 'user', 'content' => $this->param['message']],
];
}
$data = ['message' => $message];
$stream = $gptService->get_ai_chat($data); // 获取流
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$aiResponse = '';
$buffer = '';
while (!$stream->eof()) {
$chunk = $stream->read(1024);
$chunk = str_replace(chr(1), '', $chunk);
if ($chunk !== false) {
// 累积数据
$buffer .= $chunk;
// 持续解析完整的 JSON
while (preg_match('/^\{[^{}]*\}/', $buffer, $match)) {
$jsonStr = $match[0];
$jsonData = json_decode($jsonStr, true);
// 确保 JSON 解析成功
if (json_last_error() === JSON_ERROR_NONE) {
if (isset($jsonData['text'])) {
$aiResponse .= $jsonData['text'];
echo $gptService->en_sse_data(trim($jsonData['text']));
ob_flush();
flush();
}
// 移除已解析的 JSON,保留未完成的部分
$buffer = substr($buffer, strlen($jsonStr));
} else {
break;
}
}
}
}
$this->saveChatItem($chatId, $aiResponse, 1);
return true;
}
/**
... ... @@ -57,4 +120,22 @@ class ChatLogic extends BaseLogic
];
return $this->model->addReturnId($saveData);
}
/**
* @remark :消息详情表保存一条记录
* @name :saveChatItem
* @author :lyh
* @method :post
* @time :2025/4/2 13:45
*/
public function saveChatItem($id,$message,$is_reply = 2){
//创建一个会话
$saveData = [
'user_id'=>$this->user['id'],
'is_reply'=>$is_reply,
'chat_id'=>$id,
'content'=>$message,
];
return $this->itemModel->addReturnId($saveData);
}
}
... ...
<?php
/**
* @remark :
* @name :GoogleCodeCountry.php
* @author :lyh
* @method :post
* @time :2025/4/3 10:13
*/
namespace App\Models\GoogleSearch;
use App\Models\Base;
class GoogleCodeCountry extends Base
{
protected $table = 'gl_google_code_country';
/**
* @remark :获取国家中文
* @name :getCodeCountry
* @author :lyh
* @method :post
* @time :2025/4/3 10:26
*/
public function getCodeCountry($code){
$info = $this->read(['code'=>$code],['zh_country']);
if($info !== false){
return $info['zh_country'];
}
return '未知国家';
}
}
... ...
... ... @@ -23,37 +23,46 @@ class GptService
* @time :2025/4/2 9:38
*
*/
public function get_ai_chat($data,$type = 1){
// 组装请求体参数
// $data['message'] = [
// ['role' => 'system', 'content' => "You are now the marketing customer service of 深圳创贸集团"],
// ['role' => 'user', 'content' => '创贸集团有多少技术?'],
// ['role' => 'assistant', 'content' => '创贸集团有200+技术。'],
// ['role' => 'user', 'content' => '今天天气怎么样']
// ];
public function get_ai_chat($data,$type = 0){
$apikey = env('AI_CREATE_KEY')??'7yn!We6$&NnVA38bpGy*A@4TQ5iYLJcW';
$client = new CmerClient($apikey);
// 修改超时时间,默认60秒
$client->timeout=300;
$payload = new ChatModel($data['messages']);
$client->timeout = 300;
$payload = new ChatModel($data['message']);
// 修改模型名称,豆包,Gpt,Claude
$payload->model = env('CHAT_GTP_MODEL','gpt-4o-mini');;
$payload->supplier = isset($data['supplier'])?$data['supplier']:"openai";
$payload->supplier = isset($data['supplier'])?$data['supplier']:"azure";
//发送请求
if($type == 1){//返回数据
$response = $client->chat($payload);
$result=$response->getBody()->getContents();
$result = $response->getBody()->getContents();
@file_put_contents(storage_path('logs/lyh_error.log'), var_export($result, true) . PHP_EOL, FILE_APPEND);
if(!$result){
Log::info('ai接口返回错误信息:'.$result. PHP_EOL);
return false;
}
return json_decode($result,true);
}else {
//发送流式请求
// **流式请求**
$payload->stream = true;
$response = $client->chat($payload);
$body = $response->getBody();
return $body;
$stream = $response->getBody();
return $stream;
}
}
/**
* @remark :返回格式
* @name :en_sse_data
* @author :lyh
* @method :post
* @time :2025/4/2 16:56
*/
public function en_sse_data($body, string $type='text'){
return 'data:'.json_encode([
'id' => md5(is_array($body) ? json_encode($body) : $body),
'data' => $body,
'type' => $type
],JSON_UNESCAPED_UNICODE)."\n\n";
}
}
... ...
<?php
/**
* @remark :
* @name :GoogleSearchService.php
* @name :RapIdApIService.php
* @author :lyh
* @method :post
* @time :2025/3/25 11:36
... ... @@ -13,15 +13,17 @@ use App\Helper\Country;
/**
* @remark :google关键字扩展
* @name :GoogleSearchService
* @name :RapIdApIService
* @author :lyh
* @method :post
* @time :2025/3/25 11:38
*/
class GoogleSearchService
class RapIdApIService
{
public $url = "";
public $key = '3eba1ba999msh3a7c11101a7e298p19924bjsn5089487f7c37';
/**
* @remark :扩展关键词请求数据
* @name :requestUrl
... ... @@ -67,7 +69,7 @@ class GoogleSearchService
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-rapidapi-host: google-keyword-insight1.p.rapidapi.com",
"x-rapidapi-key: d246239565mshc29088b58ff484dp17c0bdjsn2d28d03622c7"
"x-rapidapi-key: $this->key"
],
]);
$response = curl_exec($curl);
... ...
... ... @@ -712,6 +712,13 @@ Route::middleware(['bloginauth'])->group(function () {
Route::any('/getEnterProduct', [\App\Http\Controllers\Bside\SeoSetting\EnterpriseProductController::class, 'getEnterProduct'])->name('enterprise_product_getEnterProduct');
Route::any('/del', [\App\Http\Controllers\Bside\SeoSetting\EnterpriseProductController::class, 'del'])->name('enterprise_product_del');
});
//大模型会话
Route::prefix('gpt')->group(function () {
Route::any('/', [\App\Http\Controllers\Bside\Gpt\ChatController::class, 'list'])->name('gpt_list');
Route::any('/itemList', [\App\Http\Controllers\Bside\Gpt\ChatController::class, 'itemList'])->name('gpt_itemList');
Route::any('/del', [\App\Http\Controllers\Bside\Gpt\ChatController::class, 'del'])->name('gpt_del');
Route::any('/sendMessage', [\App\Http\Controllers\Bside\Gpt\ChatController::class, 'sendMessage'])->name('gpt_sendMessage');
});
});
//无需登录验证的路由组
Route::group([], function () {
... ...