作者 lyh

Merge branch 'master' of http://47.244.231.31:8099/zhl/globalso-v6 into lyh-server

<?php
/**
* Created by PhpStorm.
* User: zhl
* Date: 2025/7/4
* Time: 11:14
*/
namespace App\Console\Commands\Statistics;
use App\Models\Project\ProjectFlow;
use App\Services\UpyunService;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
class Flow extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'project_flow_statistics';
/**
* The console command description.
*
* @var string
*/
protected $description = '项目流量统计';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct(); // 确保调用父类构造函数
}
public function handle()
{
$this->cdnStatistics();
return true;
}
public function cdnStatistics()
{
$class = new UpyunService();
list($start_at, $end_at) = $this->getTime();
$date = date('Y-m-d', strtotime($end_at));
echo 'start_at: ' . $start_at . PHP_EOL;
echo 'end_at: ' . $end_at . PHP_EOL;
$result = $class->realTimeStatistics($start_at, $end_at);
file_put_contents(storage_path('logs/flow/' . date('YmdHis', strtotime($start_at)) . '.json'), $result);
// $result = file_get_contents(storage_path('logs/flow/' . date('YmdHis', strtotime($start_at)) . '.json'));
$result = json_decode($result, true);
$flow = [];
if (FALSE == empty($result['data']) && is_array($result['data'])) {
// 结算 所有项目 流量
foreach ($result['data'] as $item) {
if (Str::startsWith($item['key'], '/upload/p/')) {
$tmp = explode('/', $item['key']);
$flow[$tmp[3]] = FALSE == empty($flow[$tmp[3]]) ? $flow[$tmp[3]] + $item['val'] : $item['val'];
}
}
}
ksort($flow);
$total_flow = 0;
foreach ($flow as $project_id=>$val) {
ProjectFlow::flowInsert($project_id, $date, $val, $end_at);
$total_flow += $val;
}
echo 'total project: ' . count($flow) . PHP_EOL;
echo 'total flow: ' . $total_flow . PHP_EOL;
return true;
}
/**
* @return array
*/
public function getTime()
{
$last_at_log = ProjectFlow::orderBy('last_at', 'desc')->first();
$today = date('Y-m-d 00:00:00');
$start_at = $last_at_log ? $last_at_log->last_at : $today;
if (strtotime($start_at) < strtotime($today))
$start_at = $today;
$end_at = date('Y-m-d H:i:s', time() - 1);
return [$start_at, $end_at];
}
}
\ No newline at end of file
... ...
... ... @@ -5,9 +5,11 @@ namespace App\Console\Commands\WorkOrder;
use App\Models\Manage\Manage;
use App\Models\Manage\ManageHr;
use App\Models\Project\Project;
use App\Models\ProjectAssociation\ProjectAssociation;
use App\Models\WorkOrder\TicketProject;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class FetchTicketProjects extends Command
{
... ... @@ -83,6 +85,7 @@ class FetchTicketProjects extends Command
'test_website' => $item['test_url'] ?? '',
'is_del' => 0,
'plan' => $item['plan'] ?? '',
'project_cate' => 1,
];
if (!$project) {
$new = new TicketProject();
... ... @@ -177,6 +180,11 @@ class FetchTicketProjects extends Command
'test_website' => $item->deploy_build->test_domain ?? '',
'version' => empty($item->version) ? 7 : $item->version, // 版本号
'plan' => $item->planMap()[$item->deploy_build->plan] ?? '',
'project_cate' => 2,
'wechat_group_id' => ProjectAssociation::where('project_id', $project->table_id)
->where('status', ProjectAssociation::STATUS_NORMAL)
->where('binding_app', ProjectAssociation::ENTERPRISE_WECHAT)
->value('friend_id')
];
if (!$project) {
$project = new TicketProject();
... ... @@ -209,6 +217,97 @@ class FetchTicketProjects extends Command
}
}
public function fetchAICC()
{
$lastid = 0;
while (true) {
try {
$response = Http::withBasicAuth('bill', 'bill@ai.cc')
->get('https://fob.ai.cc/api/tickets/projects', [
'lastid' => $lastid,
]);
$items = $response->json();
if (empty($items))
{
echo now() . " | INFO | not found items \n";
break;
}
foreach ($items as $item) {
$uuid = md5("AICC{$item['id']}");
$project = TicketProject::where('uuid', $uuid)->first();
// 判断套餐是超迹还是域途, 如果 $item['plans'][0]['name'] 包含 '超迹' 则为超迹,否则为域途
$project_cate = Str::contains($item['plans'][0]['name'], '超迹') ? 3 : 4;
print_r($item['cj_assm']);
print_r($item['yutu_assm']);
if ($project_cate == 3)
{
// 售后服务经理
$assm_id = collect([
ManageHr::where('name', $item['cj_assm']['real_name'] ?? '')->first()->manage_id ?? 0,
20, //徐莹
])->first(fn($v) => $v !== null && $v !== 0, 0);
}else
{
// 域途
$assm_id = collect([
ManageHr::where('name', $item['yutu_assm']['real_name'] ?? '')->first()->manage_id ?? 0,
85, //黄小玉
])->first(fn($v) => $v !== null && $v !== 0, 0);
}
// 优化师
$seom_id = 0;
// 第一负责人
$engineer_id = $assm_id;
$is_del = 0;
$fields = [
'company_name' => $item['company'],
'title' => $item['company'] . " - " . $item['plan'],
'assm_id' => $assm_id,
'seom_id' => $seom_id,
'engineer_id' => $engineer_id,
'is_del' => $is_del,
'website' => '',
'test_website' => '',
'version' => 1, // 版本号
'plan' => $item['plans'][0]['name'] ?? '',
'project_cate' => $project_cate,
'wechat_group_id' => $item['chatroom'],
];
if (!$project) {
$project = new TicketProject();
$project->uuid = $uuid;
$project->post_id = $item['postid'];
$project->table_id = $item['id'];
foreach ($fields as $k => $v) {
$project->$k = $v;
}
$project->save();
} else {
$changed = false;
foreach ($fields as $k => $v) {
if ($project->$k != $v) {
$project->$k = $v;
$changed = true;
}
}
if ($changed) {
$project->save();
}
}
$lastid = $item['id'];
echo now() . " | INFO | AICC: {$item['id']} {$item['company']} fetch ok \n";
}
}catch (\Exception $exception){
echo now() . " | ERROR | " . $exception->getMessage() . "\n" . $exception->getTraceAsString() . "\n";
break;
}
}
}
public function fetch_uuid()
{
$lastid = 0;
... ...
... ... @@ -8,8 +8,10 @@ use App\Http\Requests\Api\WorkOrder\TicketStoreRequest;
use App\Models\WorkOrder\TicketLog;
use App\Models\WorkOrder\TicketProject;
use App\Models\WorkOrder\Tickets;
use Darabonba\GatewaySpi\Models\InterceptorContext\response;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
class TicketController extends BaseController
{
... ... @@ -86,6 +88,7 @@ class TicketController extends BaseController
$log = new TicketLog();
$log->engineer_id = $project->engineer_id; // 默认第一负责人
$ticket->logs()->save($log);
$project->pushWechatGroupMsg("客户新增了工单(ID:{$ticket->id}),请及时处理!");
return $ticket;
});
return response()->json(['data' => $result]);
... ... @@ -149,4 +152,36 @@ class TicketController extends BaseController
if (!$project) return $this->response('未找到项目', 404);
return response()->json(['data' => $project]);
}
/**
* @param $friend_id
* @return void
* 企微群里@小超或艾丝,触发推送工单
* 接收群ID
*/
public function pushTicketByBot($friend_id)
{
$project = TicketProject::where('wechat_group_id', $friend_id)->where('is_del', 0)->first();
if (!$project)
return response()->json(['message' => '未找到对应的工单项目'], 404);
// $url = in_array($project->project_cate, [3,4]) ? 'https://hub.ai.cc/api/fob_ai_customer_service/push_message' : 'https://hub.ai.cc/api/globalso_ai_customer_service/send_msg';
// $response = Http::post($url, [
// 'type' => 'Link',
// 'friend_id' => $friend_id,
// 'content' => json_encode([
// 'title' => 'AI协同工单 - ' . $project->company_name,
// 'desc' => "您好,我们同事没有及时回复,你可以查看工单进度!",
// 'size' => 0,
// 'thumbSize' => 0,
// 'thumbUrl' => 'https://hub.globalso.com/logocm.png',
// 'url' => 'https://oa.quanqiusou.cn/afterorder?project_id='.$project->uuid
// ], JSON_UNESCAPED_UNICODE)
// ]);
// // 返回 $response 的相应内容以及网络状态码
// return response($response->body(), $response->status());
$project->pushWechatGroupMsg("您好,我们同事没有及时回复,你可以查看工单进度!");
return response()->json(['message' => '工单推送成功']);
}
}
... ...
... ... @@ -13,6 +13,7 @@ use App\Enums\Common\Code;
use App\Http\Controllers\Aside\BaseController;
use App\Models\Domain\DomainInfo;
use App\Models\Project\CountAllProject;
use Illuminate\Support\Facades\DB;
/**
* @remark :统计所有项目(4.0,5.0,6.0)
... ... @@ -46,6 +47,27 @@ class AllProjectController extends BaseController
$this->map['company'] = ['like','%'.$this->map['company'].'%'];
}
$data = $allProject->lists($this->map,$this->page,$this->row);
if (!empty($data['list'])) {
foreach ($data['list'] as $key => $value) {
$ticketProject = null;
if ($value['version'] == 1) {
// version 为 1:6.0
$ticketProject = DB::table('gl_ticket_projects')
->where('table_id', $value['project_id'])
->where('project_cate', 2)
->first();
} else {
// version 不为 1
$ticketProject = DB::table('gl_ticket_projects')
->where('post_id', $value['project_id'])
->where('project_cate', 1)
->first();
}
$data['list'][$key]['uuid'] = $ticketProject ? $ticketProject->uuid : null;
}
}
$this->response('success',Code::SUCCESS,$data);
}
}
... ...
... ... @@ -37,6 +37,7 @@ use App\Models\RankData\RankData;
use App\Models\Task\Task;
use App\Models\WebSetting\WebLanguage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
/**
* 项目管理
... ... @@ -69,6 +70,16 @@ class ProjectController extends BaseController
if(!empty($lists) && !empty($lists['list'])){
foreach ($lists['list'] as $k => $v){
$v = $this->handleParam($v);
// 组装 工单UUID
$ticketProject = null;
$ticketProject = DB::table('gl_ticket_projects')
->where('table_id', $v['id'])
->where('project_cate', 2)
->first();
$v['uuid'] = $ticketProject ? $ticketProject->uuid : null;
// 组装 工单UUID END
$lists['list'][$k] = $v;
}
}
... ...
... ... @@ -8,13 +8,13 @@ use App\Http\Requests\Aside\WorkOrder\AsideTicketStoreRequest;
use App\Http\Requests\Aside\WorkOrder\AsideTicketListRequest;
use App\Http\Requests\Aside\WorkOrder\AsideTicketUpdateRequest;
use App\Http\Requests\Aside\WorkOrder\TicketProjectListRequest;
use App\Models\ProjectAssociation\ProjectAssociation;
use App\Models\Workchat\MessagePush;
use App\Models\WorkOrder\TicketLog;
use App\Models\WorkOrder\TicketProject;
use App\Models\WorkOrder\Tickets;
use Illuminate\Support\Facades\DB;
class AsideTicketController extends BaseController
{
/**
... ... @@ -106,6 +106,11 @@ class AsideTicketController extends BaseController
$version = $validated['version'];
return $query->where('version', $version);
})
->when(!empty($validated['project_cate']), function ($query) use ($validated) {
// 版本号筛选
$project_cate = $validated['project_cate'];
return $query->where('project_cate', $project_cate);
})
->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
... ... @@ -143,6 +148,7 @@ class AsideTicketController extends BaseController
$log->engineer_id = $engineer_id;
$ticket->logs()->save($log);
}
$project->pushWechatGroupMsg("创贸({$ticket->submit_username})新增了工单(ID:{$ticket->id}),请及时处理!");
return $ticket;
});
$this->response('success', Code::SUCCESS, $result->toArray());
... ... @@ -232,34 +238,11 @@ class AsideTicketController extends BaseController
if (empty($project))
$this->response('工单项目不存在', Code::USER_MODEL_NOTFOUND_ERROE);
if (empty($project->association)) {
if (empty($project->wechat_group_id)) {
$this->response('该工单没有绑定的企微群', Code::USER_MODEL_NOTFOUND_ERROE);
}
$ticket = Tickets::where('project_id', $project->id)
->orderBy('id', 'desc')
->first();
$message_push = new MessagePush();
$message_push->project_id = $project->table_id;
$message_push->friend_id = $project->association->friend_id;
$message_push->content_type = 'Link';
$message_push->content = json_encode([
'title' => '工单查看 - ' . $project->company_name,
'desc' => $ticket ? $ticket->title : "工单列表",
'size' => 0,
'thumbSize' => 0,
'thumbUrl' => 'https://oa.quanqiusou.cn/logo.ico',
'url' => 'https://oa.quanqiusou.cn/afterorder?project_id='.$project->uuid
], JSON_UNESCAPED_UNICODE);
$message_push->send_time = now();
$message_push->type = MessagePush::TYPE_TICKET;
$message_push->save();
if (!empty($ticket)) {
$ticket->ding = 1; // 标记为已推送
$ticket->save();
}
$project->pushWechatGroupMsg();
$this->response('success', Code::SUCCESS);
}
}
... ...
... ... @@ -87,6 +87,8 @@ class AsideTicketLogController extends BaseController
$ticket->status = Tickets::STATUS_COMPLETED;
// 如果所有子任务都完成了,则将工单状态改为已完成
$ticket->end_at = now();
$project = $ticket->project;
$project->pushWechatGroupMsg("工单(ID:{$ticket->id})已全部完成,请访问查看详情!");
}
$ticket->save();
return $log;
... ...
... ... @@ -62,6 +62,8 @@ class TicketChatController extends BaseController
$chat->submit_side = 1;
$chat->manage_id = $this->manage['id'];
$chat->save();
$project = $ticket->project;
$project->pushWechatGroupMsg("{$chat->submit_username}对工单(ID:{$ticket->id})进行了补充,请及时查看处理!");
$this->response('success', Code::SUCCESS, $chat);
}
... ...
... ... @@ -25,7 +25,8 @@ class TicketProjectListRequest extends FormRequest
{
return [
'search' => 'nullable|string', // 搜索关键词
'version' => 'nullable|in:5,6,7', // 版本号
'project_cate' => 'nullable|in:1,2,3,4', // 项目分类:1V5,2V6,3超迹,4域途
'version' => 'nullable|integer', // 版本号
];
}
}
... ...
... ... @@ -14,4 +14,5 @@ use App\Models\Base;
class CountAllProject extends Base
{
protected $table = 'gl_all_project';
}
... ...
<?php
/**
* Created by PhpStorm.
* User: zhl
* Date: 2025/7/4
* Time: 15:05
*/
namespace App\Models\Project;
use App\Models\Base;
/**
* 流量统计
* Class ProjectFlow
* @package App\Models\Project
*/
class ProjectFlow extends Base
{
/**
* 表名
* @var string
*/
protected $table = 'gl_project_flow';
/**
* 流量日志记录
* @param $project_id
* @param $date
* @param $cdn_flow
* @param $last_at
* @return ProjectFlow
*/
public static function flowInsert($project_id, $date, $cdn_flow, $last_at)
{
$flow = self::where(['project_id' => $project_id, 'date' => $date])->first();
if (empty($flow)) {
$flow = new self();
$flow->project_id = $project_id;
$flow->date = $date;
$flow->cdn_flow = $cdn_flow;
} else {
$flow->cdn_flow = $flow->cdn_flow + $cdn_flow;
}
$flow->last_at = $last_at;
$flow->save();
return $flow;
}
}
\ No newline at end of file
... ...
... ... @@ -6,6 +6,7 @@ use App\Models\Base;
use App\Models\Manage\Manage;
use App\Models\Project\Project;
use App\Models\ProjectAssociation\ProjectAssociation;
use App\Models\Workchat\MessagePush;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class TicketProject extends Base
... ... @@ -53,4 +54,31 @@ class TicketProject extends Base
->where('binding_app', ProjectAssociation::ENTERPRISE_WECHAT)
->select(['id', 'project_id', 'friend_id', 'binding_app']);
}
/**
* @return void
* 企微群推送工单消息
*/
public function pushWechatGroupMsg($desc="可提交新的工单、查询工单进度、AI会同步通知售后人员!")
{
if (!empty($this->wechat_group_id))
{
$message_push = new MessagePush();
$message_push->project_id = $this->table_id;
$message_push->friend_id = $this->wechat_group_id;
$message_push->content_type = 'Link';
$message_push->content = json_encode([
'title' => "AI协同工单 - " . $this->company_name,
'desc' => $desc,
'size' => 0,
'thumbSize' => 0,
'thumbUrl' => 'https://hub.globalso.com/logocm.png',
'url' => 'https://oa.quanqiusou.cn/afterorder?project_id='.$this->uuid
], JSON_UNESCAPED_UNICODE);
$message_push->send_time = now();
$message_push->type = MessagePush::TYPE_TICKET;
$message_push->save();
}
}
}
... ...
... ... @@ -168,7 +168,7 @@ class ProjectAssociationServices extends BaseService
}
$param['sign'] = $this->getSign($param);
$url = 'https://hub.ai.cc/api/globalso_ai_customer_service/chatroom_list';
$result = Http::withoutVerifying()->post($url, $param)->json();
$result = Http::withoutVerifying()->withOptions(['proxy' => env('CURL_PROXY')])->post($url, $param)->json();
if(empty($result) || $result['status'] != 200){
return [];
}
... ... @@ -189,7 +189,7 @@ class ProjectAssociationServices extends BaseService
];
$param['sign'] = $this->getSign($param);
$url = 'https://hub.ai.cc/api/globalso_ai_customer_service/send_msg';
$result = Http::withoutVerifying()->timeout(30)->post($url, $param)->json();
$result = Http::withoutVerifying()->withOptions(['proxy' => env('CURL_PROXY')])->timeout(30)->post($url, $param)->json();
if(empty($result) || $result['status'] != 200){
throw new \Exception($result['message'] ?? '');
}
... ...
... ... @@ -67,6 +67,47 @@ class UpyunService
}
/**
* 统计流量
* @param $start_time
* @param $end_time
* @param string $domain
* @return mixed
*/
public function commonData($start_time, $end_time, $domain = 'ecdn6.globalso.com')
{
$action = '/flow/common_data';
$param = [
'start_time' => $start_time,
'end_time' => $end_time,
'domain' => $domain,
];
// dd($param);
list($status, $result) = $this->curlRequest($action, $param, 'GET', $this->getHeader());
return $result;
}
/**
* 统计流量
* @param $start_time
* @param $end_time
* @param string $domain
* @return mixed
*/
public function realTimeStatistics($start_time, $end_time, $domain = 'ecdn6.globalso.com')
{
$action = '/flow/real_time_statistics';
$param = [
'start_time' => $start_time,
'end_time' => $end_time,
'query_domain' => $domain,
'query_type' => 'uri_flux'
];
// dd($param);
list($status, $result) = $this->curlRequest($action, $param, 'GET', $this->getHeader());
return $result;
}
/**
* 头信息需要携带授权token
* @return array
*/
... ... @@ -88,6 +129,9 @@ class UpyunService
public function curlRequest($url, $data, $method = 'POST', $header = [], $time_out = 60)
{
$url = config('custom.upyun.api_url') . $url;
if ($method == 'GET') {
$url .= '?' . http_build_query($data);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, $time_out);
curl_setopt($ch, CURLOPT_URL, $url);
... ...
... ... @@ -86,3 +86,4 @@ Route::prefix('tickets')->group(function () {
Route::get('/chat/{project_id}/{ticket_id}', [\App\Http\Controllers\Api\WorkOrder\TicketChatController::class, 'index'])->summary('B端,渠道-工单聊天记录')->name('tickets.chat.index');
Route::post('/chat/{project_id}/{ticket_id}', [\App\Http\Controllers\Api\WorkOrder\TicketChatController::class, 'store'])->summary('B端,渠道-工单聊天记录提交')->name('tickets.chat.store');
});
Route::get('/pushTicketByBot/{friend_id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'pushTicketByBot'])->summary('企微群@机器人触发工单推送')->name('tickets.pushTicketByBot');
... ...
... ... @@ -255,8 +255,10 @@ Route::middleware(['aloginauth'])->group(function () {
Route::post('/', [Aside\WorkOrder\AsideTicketController::class, 'store'])->name('admin.tickets.store')->summary('A端创建工单');
Route::get('/{id}', [Aside\WorkOrder\AsideTicketController::class, 'show'])->name('admin.tickets.show')->summary('A端工单详情');
Route::post('/{id}', [Aside\WorkOrder\AsideTicketController::class, 'update'])->name('admin.tickets.update')->summary('A端更新工单,审核,邀请同事');
Route::get('/pushNotify/{id}', [Aside\WorkOrder\AsideTicketController::class, 'pushNotify'])->name('admin.tickets.pushNotify')->summary('A端工单推送企微群');
Route::get('/projects/{search}', [Aside\WorkOrder\AsideTicketController::class, 'getProjects'])->name('admin.tickets.projects')->summary('A端V5V6项目列表');
Route::get('/v56_projects/list', [Aside\WorkOrder\AsideTicketController::class, 'projectList'])->name('admin.tickets.projectList')->summary('A端V5V6项目列表');
Route::get('/v56_projects/list', [Aside\WorkOrder\AsideTicketController::class, 'projectList'])->name('admin.tickets.projectList')->summary('A端V5V6项目列表')
->description("project_cate[项目分类1]: 1 V5, 2 V6, 3 超迹, 4 域途");
Route::post('/log/{id}', [Aside\WorkOrder\AsideTicketLogController::class, 'update'])->name('admin.tickets.log.update')->summary('A端工单操作日志更新,完成工单');
Route::get('/chat/{ticket_id}', [Aside\WorkOrder\TicketChatController::class, 'index'])->name('admin.tickets.chat.index')->summary('A端工单聊天记录');
Route::post('/chat/{ticket_id}', [Aside\WorkOrder\TicketChatController::class, 'store'])->name('admin.tickets.chat.store')->summary('A端工单聊天记录创建');
... ...