作者 赵彬吉
... ... @@ -18,6 +18,7 @@ use App\Models\Ai\AiCommand;
use App\Models\Com\NoticeLog;
use App\Models\Com\V6WeeklyReport;
use App\Models\Product\Category;
use App\Models\Project\AggregateKeywordAffix;
use App\Models\Project\AiBlogTask;
use App\Models\Project\DeployBuild;
use App\Models\Project\DeployOptimize;
... ... @@ -58,7 +59,22 @@ class lyhDemo extends Command
protected $description = '更新路由';
public function handle(){
$aggregateKeywordAffixModel = new AggregateKeywordAffix();
$info = $aggregateKeywordAffixModel->read(['project_id'=>3298]);
$projectModel = new Project();
$lists = $projectModel->list(['delete_status' => 0,'project_type'=>0,'extend_type'=>0,'type'=>2], 'id', ['id']);
foreach ($lists as $item){
if(in_array($item['id'],[467,110,3298])){
continue;
}
$data = [
'project_id'=>$item['id'],
'prefix'=>$info['prefix'],
'suffix'=>$info['suffix']
];
$aggregateKeywordAffixModel->addReturnId($data);
}
return true;
}
public function _actionTemplateMain(){
... ...
... ... @@ -43,7 +43,7 @@ class Temp extends Command
public function handle()
{
$this->check_no_cname_projects();
$this->change_cname_projects();
}
/**
... ... @@ -600,15 +600,18 @@ class Temp extends Command
/**
* 240服务器上解析cname的项目迁移
* 服务器上解析cname的项目迁移
* @author Akun
* @date 2025/02/17 14:21
*/
public function change_cname_projects_240()
public function change_cname_projects()
{
$origin_server_id = 20;//原服务器
$target_server_id = 27;//目标服务器
$server_ip_model = new ServersIp();
$server_ip_ids = $server_ip_model->where('servers_id', 1)->get()->pluck('id')->toArray();
$server_ip_ids = $server_ip_model->where('servers_id', $origin_server_id)->get()->pluck('id')->toArray();
$project_list = Project::select(['id', 'serve_id'])->whereIn('serve_id', $server_ip_ids)->get();
... ... @@ -626,60 +629,70 @@ class Temp extends Command
$domain = $domain_info['domain'];
//迁移主站
$check = dns_get_record($domain, DNS_A);
$host = $check[0]['host'] ?? '';
if ($host == 'cname.globalso.com') {
//获取主站备份证书
$ssl_info = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $domain)->first();
if (!$ssl_info) {
$this->output('项目id:' . $project_id . ' | 未备份主站证书');
continue;
}
//创建主站建站任务
$task_info = DomainCreateTask::where('type', 1)->where('server_id', 20)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();
if (!$task_info) {
$task_model = new DomainCreateTask();
$task_model->type = 1;
$task_model->server_id = 20;
$task_model->project_id = $project_id;
$task_model->domain_id = $domain_id;
$task_model->certs = json_encode(['key' => $ssl_info->private_key, 'csr' => $ssl_info->private_cert]);
$task_model->save();
}
//创建主站页面生成任务
$notify_data = [
'project_id' => $project_id,
'type' => 1,
'route' => 1,
'server_id' => 20,
'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
];
$notify = $notify_model->read($notify_data, ['id']);
// try {
// $check = dns_get_record($domain, DNS_A);
// $host = $check[0]['host'] ?? '';
// } catch (\Exception $e) {
// $this->output($domain . ' | 获取解析记录失败');
// continue;
// }
//
// if ($host != 'cname.globalso.com') {
// $this->output($domain . ' | 未解析cname');
// continue;
// }
//获取主站备份证书
$ssl_info = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $domain)->first();
if (!$ssl_info) {
$this->output($domain . ' | 未备份证书');
continue;
}
if (!$notify) {
$notify_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);
$notify_data['status'] = Notify::STATUS_INIT;
$notify_model->add($notify_data);
}
//创建主站建站任务
$task_info = DomainCreateTask::where('type', 1)->where('server_id', $target_server_id)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();
if (!$task_info) {
$task_model = new DomainCreateTask();
$task_model->type = 1;
$task_model->server_id = $target_server_id;
$task_model->project_id = $project_id;
$task_model->domain_id = $domain_id;
$task_model->certs = json_encode(['key' => $ssl_info->private_key, 'csr' => $ssl_info->private_cert]);
$task_model->save();
}
//创建主站关键词页面生成任务
$notify_keyword_data = [
'project_id' => $project_id,
'type' => 1,
'route' => 4,
'server_id' => 20,
'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
];
$notify_keyword = $notify_model->read($notify_keyword_data, ['id']);
// //创建主站页面生成任务
// $notify_data = [
// 'project_id' => $project_id,
// 'type' => 1,
// 'route' => 1,
// 'server_id' => 20,
// 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
// ];
// $notify = $notify_model->read($notify_data, ['id']);
//
// if (!$notify) {
// $notify_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);
// $notify_data['status'] = Notify::STATUS_INIT;
// $notify_model->add($notify_data);
// }
//
// //创建主站关键词页面生成任务
// $notify_keyword_data = [
// 'project_id' => $project_id,
// 'type' => 1,
// 'route' => 4,
// 'server_id' => 20,
// 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
// ];
// $notify_keyword = $notify_model->read($notify_keyword_data, ['id']);
//
// if (!$notify_keyword) {
// $notify_keyword_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);
// $notify_keyword_data['status'] = Notify::STATUS_INIT;
// $notify_model->add($notify_keyword_data);
// }
if (!$notify_keyword) {
$notify_keyword_data['data'] = Arr::a2s(['domain' => $domain, 'url' => null, 'language' => []]);
$notify_keyword_data['status'] = Notify::STATUS_INIT;
$notify_model->add($notify_keyword_data);
}
}
if ($domain_info['amp_status'] == 1) {
//迁移amp站
... ... @@ -693,44 +706,53 @@ class Temp extends Command
}
$amp_domain = implode('.', $host_array);
$check_amp = dns_get_record($amp_domain, DNS_A);
$host_amp = $check_amp[0]['host'] ?? '';
if ($host_amp == 'cname.globalso.com') {
//获取amp站备份证书
$ssl_info_amp = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $amp_domain)->first();
if (!$ssl_info_amp) {
$this->output('项目id:' . $project_id . ' | 未备份amp站证书');
continue;
}
//创建amp站建站任务
$task_info_amp = DomainCreateTask::where('type', 2)->where('server_id', 20)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();
if (!$task_info_amp) {
$task_model = new DomainCreateTask();
$task_model->type = 2;
$task_model->server_id = 20;
$task_model->project_id = $project_id;
$task_model->domain_id = $domain_id;
$task_model->certs = json_encode(['key' => $ssl_info_amp->private_key, 'csr' => $ssl_info_amp->private_cert]);
$task_model->save();
}
// try {
// $check_amp = dns_get_record($amp_domain, DNS_A);
// $host_amp = $check_amp[0]['host'] ?? '';
// } catch (\Exception $e) {
// $this->output($amp_domain . ' | 获取解析记录失败');
// continue;
// }
//
// if ($host_amp != 'cname.globalso.com') {
// $this->output($amp_domain . ' | 未解析cname');
// continue;
// }
//获取amp站备份证书
$ssl_info_amp = DB::table('gl_domain_ssl_backup')->select(['private_key', 'private_cert'])->where('domain', $amp_domain)->first();
if (!$ssl_info_amp) {
$this->output($amp_domain . ' | 未备份证书');
continue;
}
//创建amp站页面生成任务
$notify_amp_data = [
'project_id' => $project_id,
'type' => 3,
'route' => 1,
'server_id' => 20,
'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
];
$notify_amp = $notify_model->read($notify_amp_data, ['id']);
if (!$notify_amp) {
$notify_amp_data['data'] = Arr::a2s(['domain' => $amp_domain, 'url' => null, 'language' => []]);
$notify_amp_data['status'] = Notify::STATUS_INIT;
$notify_model->add($notify_amp_data);
}
//创建amp站建站任务
$task_info_amp = DomainCreateTask::where('type', 2)->where('server_id', $target_server_id)->where('project_id', $project_id)->where('status', '!=', DomainCreateTask::STATUS_SUC)->first();
if (!$task_info_amp) {
$task_model = new DomainCreateTask();
$task_model->type = 2;
$task_model->server_id = $target_server_id;
$task_model->project_id = $project_id;
$task_model->domain_id = $domain_id;
$task_model->certs = json_encode(['key' => $ssl_info_amp->private_key, 'csr' => $ssl_info_amp->private_cert]);
$task_model->save();
}
// //创建amp站页面生成任务
// $notify_amp_data = [
// 'project_id' => $project_id,
// 'type' => 3,
// 'route' => 1,
// 'server_id' => 20,
// 'status' => ['!=', Notify::STATUS_FINISH_SITEMAP]
// ];
// $notify_amp = $notify_model->read($notify_amp_data, ['id']);
//
// if (!$notify_amp) {
// $notify_amp_data['data'] = Arr::a2s(['domain' => $amp_domain, 'url' => null, 'language' => []]);
// $notify_amp_data['status'] = Notify::STATUS_INIT;
// $notify_model->add($notify_amp_data);
// }
}
}
}
... ...
<?php
namespace App\Console\Commands\WorkOrder;
use App\Models\Manage\Manage;
use App\Models\Project\Project;
use App\Models\WorkOrder\TicketProject;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class FetchTicketProjects extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'workorder:fetch-ticket-projects {version}';
/**
* The console command description.
*
* @var string
*/
protected $description = '同步V5,V6的项目到 gl_ticket_projects 表';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$version = $this->argument('version');
if ($version == 'v5') {
$this->fetch_v5();
} elseif ($version == 'v6') {
$this->fetch_v6();
} else {
$this->error('Invalid action. Use "v5" or "v6".');
return 1;
}
return 0;
}
/**
* @return void
* 请求:https://www.quanqiusou.cn/extend_api/webs/globalso_all.php
*/
public function fetch_v5()
{
# pm 项目经理 assm 售后服务经理
$response = Http::get('https://www.quanqiusou.cn/extend_api/webs/globalso_all.php');
if ($response->status() == 200) {
$items = $response->json();
foreach ($items as $item) {
# V5: 版本号+postid
$uuid = md5("V5{$item['postid']}");
$project = TicketProject::where('uuid', $uuid)->first();
$item['pm'] = $item['pm'] == '未安排' ? '杨长远' : $item['pm'];
$item['assm'] = $item['assm'] == '未安排' ? '杨长远' : $item['assm'];
$item['yhs'] = $item['yhs'] == '未安排' ? '杨长远' : $item['yhs'];
// 如果 $item['cate'] 包含”推广“字符,则$engineer_name = $item['assm']
$engineer_name = (strpos($item['cate'], '推广') !== false) ? $item['yhs'] : $item['assm'];
$fields = [
'post_id' => $item['postid'],
'company_name' => $item['company'],
'title' => $item['title'],
'engineer_id' => Manage::where('name', $engineer_name)->value('id') ?? 0, // 第一负责人
'assm_id' => Manage::where('name', $item['assm'])->value('id') ?? 0, //售后服务经理
'seom_id' => Manage::where('name', $item['yhs'])->value('id') ?? 0, //售后服务经理
'website' => $item['main_url'] ?? '',
];
if (!$project) {
$new = new TicketProject();
$new->uuid = $uuid;
$new->version = 5;
$new->table_id = 0;
foreach ($fields as $k => $v) {
$new->$k = $v;
}
$new->save();
} else {
$changed = false;
foreach ($fields as $k => $v) {
if ($project->$k != $v) {
$project->$k = $v;
$changed = true;
}
}
if ($changed) {
$project->save();
}
}
echo "V5: {$item['postid']} - {$item['title']} - {$item['company']} - {$item['main_url']}\n";
}
}
}
/**
* @return void
* 1. 按照ID升序查询 gl_project 表 limit 10
* 2。同步到 TicketProject 后,redis 缓存 ID
*/
public function fetch_v6()
{
$lastid = 0;
while (true) {
try {
$items = Project::where('id', '>', intval($lastid))
->orderBy('id', 'asc')
->limit(10)
->get();
if ($items->isEmpty()) {
echo "not found items \n";
break;
}
foreach ($items as $item) {
$uuid = md5("V5{$item->id}");
$project = TicketProject::where('uuid', $uuid)->first();
$fields = [
'company_name' => $item->company,
'title' => $item->title,
'assm_id' => $item->type ==3 ? $item->deploy_optimize->manager_mid ?? 0 : $item->deploy_build->manager_mid ?? 0, // 售后服务经理
'seom_id' => $item->deploy_optimize->optimist_mid ?? $item->deploy_optimize->manager_mid ?? $item->deploy_optimize->tech_leader ?? 0, // 优化推广负责人
'engineer_id' => $item->type == 3 ?
$item->deploy_optimize->optimist_mid ?? $item->deploy_optimize->manager_mid ?? $item->deploy_optimize->tech_leader ?? 0
: $item->deploy_build->manager_mid ?? $item->deploy_build->leader_mid ?? 0, // 技术组长
];
if (!$project) {
$project = new TicketProject();
$project->uuid = $uuid;
$project->post_id = $item->post_id;
$project->version = 6;
$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 date('Y-m-d H:i:s') . " V6: $item->id {$item->company} fetch ok \n";
}
}catch (\Exception $exception) {
echo $exception;
break;
}
}
}
}
... ...
<?php
namespace App\Http\Controllers\Api\WorkOrder;
use App\Http\Controllers\Api\BaseController;
use App\Http\Requests\Api\WorkOrder\TicketStoreRequest;
use App\Models\WorkOrder\TicketLog;
use App\Models\WorkOrder\TicketProject;
use App\Models\WorkOrder\Tickets;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class TicketController extends BaseController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index($project_id, Request $request)
{
$project = TicketProject::where('uuid', $project_id)->first();
if (!$project) return $this->error('未找到项目', 404);
$page = (int)$request->input('page', 1);
$size = (int)$request->input('size', 10);
$tickets = Tickets::with([
'project.projectV6:id,company,version',
'logs.engineer:id,name',
])
->where('project_id', $project->id)
->orderBy('id', 'desc')
->paginate($size, ['*'], 'page', $page);
return response()->json(['data' => $tickets]);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* B端用户在企微群里提交工单
*/
public function store($project_id, TicketStoreRequest $request)
{
$request->validated();
$project = TicketProject::where('uuid', $project_id)->first();
if (!$project) return $this->error('未找到项目', 404);
if ($project->version == 6){
if ($project->project->projectV6->delete_status) return $this->error('该项目已被删除', 400);
if ($project->project->projectV6->extend_type == 5) return $this->error('未续费', 400);
if ($project->project->projectV6->type == 8) return $this->error('项目已归档', 400);
if ($project->project->projectV6->site_status == 1) return $this->error('站点已关闭', 400);
}
$result = DB::transaction(function () use ($request, $project) {
$ticket = new Tickets();
$ticket->project_id = $project->id;
$ticket->title = $request->input('title');
$ticket->content = $request->input('content');
// $files = [NULL]
$files = $request->input('files');
if (empty($files) || (is_array($files) && count(array_filter($files, function($v){ return !is_null($v); })) === 0)) {
$ticket->files = null;
} else {
$ticket->files = json_encode($files);
}
$ticket->submit_side = 2; // 2 for B-side submission
$ticket->submit_username = $request->input('submit_username');
$ticket->save();
$log = new TicketLog();
if ($project->version == 5){
# V5
$log->engineer_id = $project->engineer_id;
}else{
# V6 的项目
if ($project->projectV6->type == 3){
// 项目类型是优化推广,项目负责人找优化
$seo = $project->projectV6->deploy_optimize;
$log->engineer_id = $seo->manager_mid ?? $seo->optimist_mid ?? 0;
}else{
// 非优化推广项目,项目负责人找技术组长
$build = $project->projectV6->deploy_build;
$log->engineer_id = $build->leader_mid ?? 0;
}
}
$ticket->logs()->save($log);
return $ticket;
});
return response()->json(['data' => $result]);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($project_id, $id)
{
$ticket = Tickets::with([
'logs.engineer:id,name',
'project.projectV6:id,company',
])
->find($id);
if (!$ticket) return $this->error('工单未找到', 404);
if ($ticket->project->uuid !== $project_id) return $this->error('无权限查看该工单', 403);
if ($ticket->project->version == 6){
if ($ticket->project->projectV6->delete_status) return $this->error('该项目已被删除', 400);
if ($ticket->project->projectV6->extend_type == 5) return $this->error('未续费', 400);
if ($ticket->project->projectV6->type == 8) return $this->error('项目已归档', 400);
if ($ticket->project->projectV6->site_status == 1) return $this->error('站点已关闭', 400);
}
return response()->json(['data' => $ticket]);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
public function projectInfo($project_id)
{
$project = TicketProject::with([
'projectV6:id,company',
'assm:id,name',
'seom:id,name',
'first_engineer:id,name',
])
->where('uuid', $project_id)->first();
if (!$project) return $this->error('未找到项目', 404);
return response()->json(['data' => $project]);
}
}
... ...
... ... @@ -12,10 +12,23 @@ namespace App\Http\Controllers\Aside\Com;
use App\Enums\Common\Code;
use App\Http\Controllers\Aside\BaseController;
use App\Models\Blog\Blog;
use App\Models\Blog\BlogCategory;
use App\Models\CustomModule\CustomModuleCategory;
use App\Models\CustomModule\CustomModuleContent;
use App\Models\Domain\DomainInfo;
use App\Models\News\News;
use App\Models\News\NewsCategory;
use App\Models\Product\Category;
use App\Models\Product\CategoryRelated;
use App\Models\Product\Keyword;
use App\Models\Product\Product;
use App\Models\Project\Country as CountryModel;
use App\Models\Project\DeployBuild;
use App\Models\WebSetting\SettingNum;
use App\Models\WebSetting\TranslateBigProject;
use App\Models\WebSetting\WebLanguage;
use App\Services\ProjectServer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
... ... @@ -30,6 +43,69 @@ use Illuminate\Support\Facades\DB;
class CNoticeController extends BaseController
{
/**
* @remark :计算页面数量
* @name :countLanguagePage
* @author :lyh
* @method :post
* @time :2025/6/19 17:14
*/
public function countLanguagePage(){
$this->request->validate([
'language'=>'required',
'project_id'=>'required',
],[
'language.required' => 'language不能为空',
'project_id.required' => 'project_id不能为空',
]);
$bigProjectModel = new TranslateBigProject();
$project_id_arr = $bigProjectModel->selectField(['status'=>1],'project_id') ?? [];
if(in_array($this->param['project_id'],$project_id_arr)){
$this->response('success');
}
$lang_num = count($this->param['language']);
ProjectServer::useProject($this->param['project_id']);
$keyword_num = (new Keyword())->counts(['route'=>['!=',null]]);
$data_num = $this->productNum() + $this->customNum() + $this->newsNum() + $this->blogNum();
DB::disconnect('custom_mysql');
$number = $keyword_num * 18 + $lang_num * $data_num;
if($number >= 450000){
$this->response('success',Code::SUCCESS,['msg'=>'翻译数量过多, 大概页面数量:'.$number.', 磁盘空间占用可能会超过40G,请联系管理员操作!']);
}
$this->response('success');
}
/**
* @remark :产品分类页面数量
* @name :productCateNum
* @author :lyh
* @method :post
* @time :2025/1/4 10:43
*/
public function productNum(){
$number = (new Product())->counts(['status'=>1]);
$settingModel = new SettingNum();
$info = $settingModel->read(['type'=>1]);
if($info === false){
$product_page_number = 15;
}else{
$product_page_number = $info['num'];
}
$productCateModel = new Category();
$productCateList = $productCateModel->list(['status'=>1]);
if(!empty($productCateList)){
$cateReModel = new CategoryRelated();
foreach ($productCateList as $v){
$cate_num = $cateReModel->counts(['cate_id'=>$v['id']]);
if($cate_num == 0){
$number += 1;
}else{
$number += ceil($cate_num / $product_page_number);
}
}
}
return $number;
}
/**
* 更新通知C端
* @param Request $request
* @return \Illuminate\Http\JsonResponse
... ... @@ -58,8 +134,102 @@ class CNoticeController extends BaseController
http_post($url, json_encode($param));
$this->response('更新中请稍后, 更新完成将会发送站内信通知更新结果!');
}
/**
* @remark :新闻数量
* @name :newsNum
* @author :lyh
* @method :post
* @time :2025/1/4 11:21
*/
public function newsNum(){
$newsModel = new News();
$number = $newsModel->counts(['status'=>1]);
$settingModel = new SettingNum();
$info = $settingModel->read(['type'=>2]);
if($info === false){
$news_page_number = 10;
}else{
$news_page_number = $info['num'];
}
$newsCateModel = new NewsCategory();
$newsCateList = $newsCateModel->list(['status'=>0]);
if(!empty($newsCateList)){
foreach ($newsCateList as $v){
$cate_num = $newsModel->counts(['category_id'=>['like',','.$v['id'].',']]);
if($cate_num == 0){
$number += 1;
}else{
$number += ceil($cate_num / $news_page_number);
}
}
}
return $number;
}
/**
* @remark :博客数量
* @name :blogNum
* @author :lyh
* @method :post
* @time :2025/1/4 11:21
*/
public function blogNum(){
$blogModel = new Blog();
$number = $blogModel->counts(['status'=>1]);
$settingModel = new SettingNum();
$info = $settingModel->read(['type'=>3]);
if($info === false){
$news_page_number = 10;
}else{
$news_page_number = $info['num'];
}
$blogCateModel = new BlogCategory();
$blogCateList = $blogCateModel->list(['status'=>0]);
if(!empty($blogCateList)){
foreach ($blogCateList as $v){
$cate_num = $blogModel->counts(['category_id'=>['like',','.$v['id'].',']]);
if($cate_num == 0){
$number += 1;
}else{
$number += ceil($cate_num / $news_page_number);
}
}
}
return $number;
}
/**
* @remark :扩展模块数量
* @name :blogNum
* @author :lyh
* @method :post
* @time :2025/1/4 11:21
*/
public function customNum(){
$contentModel = new CustomModuleContent();
$number = $contentModel->counts(['status'=>0]);
$settingModel = new SettingNum();
$info = $settingModel->read(['type'=>2]);
if($info === false){
$news_page_number = 10;
}else{
$news_page_number = $info['num'];
}
$cateModel = new CustomModuleCategory();
$cateList = $cateModel->list(['status'=>0]);
if(!empty($cateList)){
foreach ($cateList as $v){
$cate_num = $contentModel->counts(['category_id'=>['like',','.$v['id'].',']]);
if($cate_num == 0){
$number += 1;
}else{
$number += ceil($cate_num / $news_page_number);
}
}
}
return $number;
}
/**
* @remark :获取当前项目选中的语种
* @name :getCountry
* @author :lyh
... ...
<?php
namespace App\Http\Controllers\Aside\WorkOrder;
use App\Enums\Common\Code;
use App\Http\Controllers\Aside\BaseController;
use App\Http\Requests\Aside\WorkOrder\AsideTicketStoreRequest;
use App\Http\Requests\Aside\WorkOrder\AsideTicketListRequest;
use App\Http\Requests\Aside\WorkOrder\AsideTicketUpdateRequest;
use App\Models\WorkOrder\TicketLog;
use App\Models\WorkOrder\TicketProject;
use App\Models\WorkOrder\Tickets;
use Illuminate\Support\Facades\DB;
class AsideTicketController extends BaseController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(AsideTicketListRequest $request)
{
/**
* 1. 超管看所有工单
* 2. 其他查看和自己有关的工单
*/
$lists = TicketLog::with([
'ticket.project.projectV6:id,company,title',
'ticket.logs.engineer:id,name',
])
->when($this, function ($query) {
$role = $this->manage['role'];
// 超管 role = 1
if ($role == 1) {
return $query;
}
// 其他角色查自己参与的工单
return $query->where('engineer_id', $this->manage['id']);
})
->when($request->input('project_id') !== null, function ($query) use ($request) {
// project_id 查 gl_ticket_projects.uuid
return $query->whereHas('ticket.project', function ($q) use ($request) {
$q->where('uuid', $request->input('project_id'));
});
})
->when($request->input('status') !== null, function ($query) use ($request) {
// status 查 gl_tickets.status
return $query->whereHas('ticket', function ($q) use ($request) {
$q->where('status', $request->input('status'));
});
})
->when($request->input('search'), function ($query) use ($request) {
// search 查 gl_tickets.title 或 gl_ticket_projects.title 或 gl_ticket_projects.company_name
$search = $request->input('search');
$query->where(function ($q) use ($search) {
$q->whereHas('ticket', function ($q1) use ($search) {
$q1->where('title', 'like', '%' . $search . '%');
})
->orWhereHas('ticket.project', function ($q2) use ($search) {
$q2->where('title', 'like', '%' . $search . '%')
->orWhere('company_name', 'like', '%' . $search . '%');
});
});
})
->orderBy('id', 'desc')
->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
public function getProjects($search)
{
$projects = TicketProject::with([
'projectV6:id,company,title',
])
->where(function ($query) use ($search) {
$query->where('title', 'like', '%' . $search . '%')
->orWhere('company_name', 'like', '%' . $search . '%')
->orWhereHas('projectV6', function ($q) use ($search) {
$q->where('company', 'like', '%' . $search . '%')
->orWhere('title', 'like', '%' . $search . '%');
});
})
->get();
$this->response('success', Code::SUCCESS, $projects);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(AsideTicketStoreRequest $request)
{
$request->validated();
$project = TicketProject::where('uuid', $request->input('project_id'))->first();
if ($project->version == 6){
if ($project->projectV6->delete_status) $this->response('该项目已被删除', Code::USER_MODEL_NOTFOUND_ERROE);
if ($project->projectV6->extend_type == 5) $this->response('未续费', Code::USER_MODEL_NOTFOUND_ERROE);
if ($project->projectV6->type == 8) $this->response('项目已归档', Code::USER_MODEL_NOTFOUND_ERROE);
if ($project->projectV6->site_status == 1) $this->response('站点已关闭', Code::USER_MODEL_NOTFOUND_ERROE);
}
$result = DB::transaction(function () use ($request, $project) {
$ticket = new Tickets();
$ticket->project_id = $project->id;
$ticket->title = $request->input('title');
$ticket->content = $request->input('content');
// $files = [NULL]
$files = $request->input('files');
if (empty($files) || (is_array($files) && count(array_filter($files, function($v){ return !is_null($v); })) === 0)) {
$ticket->files = null;
} else {
$ticket->files = json_encode($files);
}
$ticket->submit_side = 1; // 1 for A-side submission
$ticket->submit_user_id = $this->manage['id'];
$ticket->submit_username = $this->manage['name'];
$ticket->save();
// A 端提工单,都是针对客户提的需求等开发任务;比如翻译,修改页面等。。。
foreach ($request->input('engineer_ids', []) as $engineer_id) {
$log = new TicketLog();
$log->engineer_id = $engineer_id;
$ticket->logs()->save($log);
}
return $ticket;
});
$this->response('success', Code::SUCCESS, $result->toArray());
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$ticket = Tickets::with([
'logs.engineer',
'project.projectV6:id,company,title',
])->find($id);
if (!$ticket) $this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
if ($this->manage['role'] != 1
&& $ticket->submit_user_id != $this->manage['id']
&& $ticket->logs()->where('engineer_id', $this->manage['id'])->count() == 0)
// 只能查看自己的工单
$this->response('没有权限查看该工单', Code::USER_PERMISSION_ERROE);
// TODO 判断是否有查看工单详情权限,待添加
$this->response('success', Code::SUCCESS, $ticket->toArray());
}
/**
* A端修改工单
* 1. 邀请协同的同事
* 2. 审核工单
*/
public function update(AsideTicketUpdateRequest $request, $id)
{
$request->validated();
$ticket = Tickets::find($id);
if (!$ticket) $this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
// 检测修改权限
if ($ticket->submit_side == 1 && $ticket->submit_user_id != $this->manage['id']) {
// A端提交的工单,只有提交人可以修改
$this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
} elseif ($ticket->submit_side == 2)
{
// B端提交的工单,只有第一对接人可以修改
$log = $ticket->logs()->first();
if ($log->engineer_id != $this->manage['id'])
$this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
}
// 开始修改
$result = DB::transaction(function () use ($request, $ticket) {
if ($request->input('engineer_ids'))
{
// 有邀请工程师协同处理
foreach ($request->input('engineer_ids') as $engineer_id)
{
try {
// 利用唯一索引去重
$new_log = new TicketLog();
$new_log->engineer_id = $engineer_id;
$ticket->logs()->save($new_log);
}catch (\Exception $exception){}
}
}
$ticket->reply = $request->input('reply', null);
$ticket->status = $request->input('status', $ticket->status);
$ticket->save();
if ($ticket->status == Tickets::STATUS_COMPLETED)
{
// 完成工单,把子任务里面未完成的工单改为完成
$ticket->end_at = now();
$ticket->logs()->where('status', '<', TicketLog::STATUS_COMPLETED)
->update(['status' => TicketLog::STATUS_COMPLETED, 'end_at' => now()]);
}
return $ticket;
});
$this->response('success', Code::SUCCESS, $result->toArray());
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
... ...
<?php
namespace App\Http\Controllers\Aside\WorkOrder;
use App\Enums\Common\Code;
use App\Http\Controllers\Aside\BaseController;
use App\Http\Requests\Aside\WorkOrder\AsideTicketLogUpdateRequest;
use App\Models\WorkOrder\TicketLog;
use App\Models\WorkOrder\Tickets;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class AsideTicketLogController extends BaseController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* A 端完结工单任务
*/
public function update(AsideTicketLogUpdateRequest $request, $id)
{
$log = TicketLog::find($id); // 拆分的子工单
if (!$log) {
$this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
}
if ($log->engineer_id != $this->manage['id']) {
// 只能操作自己的工单
$this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
}
if ($log->status >= TicketLog::STATUS_COMPLETED) {
// 已经完成的工单不能再操作
$this->response('工单已完成,不能再操作', Code::USER_PERMISSION_ERROE);
}
$ticket = $log->ticket;
$result = DB::transaction(function () use ($request, $ticket, $log) {
if ($request->input('status') !== null)
{
$log->status = $request->input('status');
if ($log->status >= TicketLog::STATUS_COMPLETED)
{
// 我的工单标记为已完成
$log->status = $request->input('status');
$log->end_at = now();
}
}
$log->save();
// 是否有未完成的子任务
$pending = $ticket->logs()
->where('status', '<', TicketLog::STATUS_COMPLETED)
->count();
if ($pending)
{
$ticket->status = Tickets::STATUS_PROCESSING;
}else
{
$ticket->status = Tickets::STATUS_COMPLETED;
// 如果所有子任务都完成了,则将工单状态改为已完成
$ticket->end_at = now();
}
$ticket->save();
return $log;
});
$this->response('success', Code::SUCCESS, $result->toArray());
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
... ...
<?php
namespace App\Http\Requests\Api\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class TicketStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required|string',
'content' => 'required|string',
'files' => 'nullable|array',
'submit_username' => 'required|string',
];
}
}
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class AsideTicketListRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'project_id' => 'nullable|string', // 产品ID
'status' => 'nullable|in:0,1,2,3|integer',
'search' => 'nullable|string', // 搜索关键词
'page' => 'nullable|integer',
'size' => 'nullable|integer',
];
}
}
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class AsideTicketLogUpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'status' => 'required|in:0,1,2,3|integer',
'reply' => 'nullable|string',
];
}
}
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class AsideTicketStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'project_id' => 'required|exists:gl_ticket_projects,uuid',
'title' => 'required|string',
'content' => 'required|string',
'files' => 'nullable|array',
'engineer_ids' => 'array',
];
}
}
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class AsideTicketUpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'status' => 'nullable|in:0,1,2,3|integer',
'reply' => 'nullable|string',
'engineer_ids' => 'nullable|array',
];
}
}
... ...
<?php
namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class TicketLog extends Base
{
use HasFactory;
protected $table = 'gl_ticket_logs';
const STATUS_PEDDING = 0; // 待处理
const STATUS_PROCESSING = 1; // 处理中
const STATUS_COMPLETED = 2; // 已完成
const STATUS_CLOSED = 3; // 已关闭
public function engineer()
{
return $this->belongsTo(Manage::class, 'engineer_id', 'id')
->select(['id', 'name']);
}
public function ticket()
{
return $this->belongsTo(Tickets::class, 'ticket_id', 'id');
}
}
... ...
<?php
namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
use App\Models\Project\Project;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class TicketProject extends Base
{
use HasFactory;
protected $table = 'gl_ticket_projects';
public function projectV6()
{
return $this->hasOne(Project::class, 'id', 'table_id')
->where('version', 6);
}
//售后服务经理
public function assm()
{
return $this->hasOne(Manage::class, 'id', 'assm_id')
->select(['id', 'name']);
}
// 优化师
public function seom()
{
return $this->hasOne(Manage::class, 'id', 'seom_id')
->select(['id', 'name']);
}
/**
* 第一负责人
*/
public function first_engineer()
{
return $this->hasOne(Manage::class, 'id', 'engineer_id')
->select(['id', 'name']);
}
}
... ...
<?php
namespace App\Models\WorkOrder;
use App\Models\Base;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Tickets extends Base
{
use HasFactory;
protected $table = 'gl_tickets';
const STATUS_PEDDING = 0; // 待处理
const STATUS_PROCESSING = 1; // 处理中
const STATUS_COMPLETED = 2; // 已完成
const STATUS_CLOSED = 3; // 已关闭
/**
* @return void
* 关联的工单日志
*/
public function logs()
{
return $this->hasMany(TicketLog::class, 'ticket_id', 'id');
}
/**
* 关联项目
*/
public function project()
{
return $this->belongsTo(TicketProject::class, 'project_id', 'id');
}
}
... ...
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketProjectsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('gl_ticket_projects', function (Blueprint $table) {
$table->id();
$table->integer('post_id')->index();
$table->integer('version')->default(5)->comment('版本号: 5, 6');
$table->integer('table_id')->index()->comment('来源表ID');
$table->string('uuid', 32)->unique()->comment('项目链接唯一标识符');
$table->string('company_name')->nullable()->index()->comment('公司名称');
$table->string('title')->nullable()->index()->comment('项目标题');
$table->bigInteger('engineer_id')->default(0)->index()->comment('V5项目的第一负责人 gl_manage 表ID');
$table->string('website')->nullable()->index()->comment('站点域名');
$table->timestamps();
});
\Illuminate\Support\Facades\DB::statement("ALTER TABLE `gl_ticket_projects` comment '售后工单项目整合表V5,V6'");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('gl_ticket_projects');
}
}
... ...
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('gl_tickets', function (Blueprint $table) {
$table->id();
$table->foreignId('project_id')->constrained('gl_ticket_projects')->onDelete('cascade')->comment('项目ID');
$table->string('title')->nullable()->comment('工单类型,工单标题');
$table->longText('content')->comment('工单图文描述');
$table->json('files')->nullable()->comment('附件');
$table->integer('status')->index()->default(0)->comment('工单状态,0:待处理, 1:处理中,2:已完成, 3:已关闭');
$table->integer('submit_side')->default(1)->index()->comment('提交方,1: A端, 2: B端');
$table->string('submit_username')->nullable()->comment('提交人姓名,B端在企微群提交时,留个姓名即可');
$table->integer('submit_user_id')->default(0)->index()->comment('A端提交时,提交人 gl_manage ID');
$table->timestamp('end_at')->nullable()->comment('结束时间,工单状态为已完成或已关闭时有值');
$table->longText('reply')->nullable()->comment('回复内容,审核意见,可以为空');
$table->timestamps();
});
\Illuminate\Support\Facades\DB::statement("ALTER TABLE `gl_tickets` comment '工单表,注意:B端,A端都会提工单'");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('gl_tickets');
}
}
... ...
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTicketLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('gl_ticket_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_id')->constrained('gl_tickets')->onDelete('cascade')->comment('工单ID');
$table->unsignedInteger('engineer_id')->comment('处理人 gl_manage ID');
$table->foreign('engineer_id')->references('id')->on('gl_manage')->onDelete('cascade');
$table->longText('reply')->nullable()->comment('回复内容可以为空');
$table->integer('status')->default(0)->index()->comment('工单状态,0:待处理, 1:处理中,2:已完成, 3:已关闭');
$table->boolean('ding')->default(false)->index()->comment('钉钉通知');
$table->timestamp('end_at')->nullable()->comment('结束时间,工单状态为已完成或已关闭时有值');
$table->unique(['ticket_id', 'engineer_id'], 'ticket_engineer_unique'); # 唯一索引,防止同一工单被同一人多次操作,同一工单,可以多人协作
$table->timestamps();
});
\Illuminate\Support\Facades\DB::statement("ALTER TABLE `gl_ticket_logs` comment '工单操作日志表,记录工单的分配处理任务等'");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('gl_ticket_logs');
}
}
... ...
... ... @@ -75,4 +75,12 @@ Route::post('/inquiry_relate_domain', [\App\Http\Controllers\Api\PrivateControll
Route::get('/get_manage_by_domain', [\App\Http\Controllers\Api\PrivateController::class, 'getProjectManageByDomain']);
// 获取信息通过商户号
Route::any('/get_project_by_mch_id', [\App\Http\Controllers\Api\PrivateController::class, 'getProjectByMchId']);
\ No newline at end of file
Route::any('/get_project_by_mch_id', [\App\Http\Controllers\Api\PrivateController::class, 'getProjectByMchId']);
// B端,渠道在企微群操作-售后工单
Route::prefix('tickets')->group(function () {
Route::get('/{project_id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'index'])->summary('B端,渠道-工单列表')->name('tickets.list');
Route::get('/projectInfo/{project_id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'projectInfo'])->summary('B端,渠道-项目信息')->name('tickets.projectInfo');
Route::post('/{project_id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'store'])->summary('B端,渠道-提工单')->name('tickets.store');
Route::get('/{project_id}/{id}', [\App\Http\Controllers\Api\WorkOrder\TicketController::class, 'show'])->summary('B端,渠道-工单详情')->name('tickets.show');
});
... ...
... ... @@ -15,6 +15,7 @@ Route::middleware(['aloginauth'])->group(function () {
Route::any('/logout', [Aside\LoginController::class, 'logout'])->name('admin.logout.white');
Route::any('/getAccessAddress', [Aside\LoginController::class, 'getAccessAddress'])->name('admin.getAccessAddress');//获取B端地址
Route::any('/sendNotify', [Aside\Com\CNoticeController::class, 'sendNotify'])->name('admin.sendNotify');
Route::any('/countLanguagePage', [Aside\Com\CNoticeController::class, 'countLanguagePage'])->name('admin.countLanguagePage');//统计页面数量
Route::any('/getCountry', [Aside\Com\CNoticeController::class, 'getCountry'])->name('admin.getCountry');
Route::any('/getDynamicPassword', [Aside\Com\IndexController::class, 'getDynamicPassword'])->name('admin.getDynamicPassword');
Route::any('/notAiHumanizer', [Aside\Com\IndexController::class, 'notAiHumanizer'])->name('admin.notAiHumanizer');
... ... @@ -255,6 +256,16 @@ Route::middleware(['aloginauth'])->group(function () {
Route::get('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'show'])->name('admin.workorder.show')->summary('A端工单详情');
Route::post('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'update'])->name('admin.workorder.update')->summary('A端更新工单');
});
// 售后工单改版
Route::prefix('tickets')->group(function () {
Route::get('/', [Aside\WorkOrder\AsideTicketController::class, 'index'])->name('admin.tickets.index')->summary('A端工单列表');
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('/projects/{search}', [Aside\WorkOrder\AsideTicketController::class, 'getProjects'])->name('admin.tickets.projects')->summary('A端V5V6项目列表');
Route::post('/log/{id}', [Aside\WorkOrder\AsideTicketLogController::class, 'update'])->name('admin.tickets.log.update')->summary('A端工单操作日志更新,完成工单');
});
//服务器配置
Route::prefix('devops')->group(function () {
Route::any('/', [Aside\Devops\ServerConfigController::class, 'lists'])->name('admin.devops.lists');
... ...