作者 zhl

Merge branch 'master' of 47.244.231.31:zhl/globalso-v6 into zhl

正在显示 53 个修改的文件 包含 2143 行增加167 行删除
... ... @@ -13,6 +13,7 @@ use App\Helper\Arr;
use App\Models\Ai\AiBlog;
use App\Models\Ai\AiBlogAuthor;
use App\Models\Ai\AiBlogList;
use App\Models\Com\NoticeLog;
use App\Models\Com\Notify;
use App\Models\Devops\ServerConfig;
use App\Models\Devops\ServersIp;
... ... @@ -337,6 +338,9 @@ class AiBlogTask extends Command
'is_sitemap' => 0
];
$res = http_post($c_url, json_encode($param,true));
if(empty($res)){
NoticeLog::createLog(NoticeLog::GENERATE_PAGE, json_encode(['c_url'=>$c_url,'c_params'=>$param]),date('Y-m-d H:i:s',time()+300));
}
$this->output('notify: project id: ' . $project_id . ', result: ' . json_encode($res,JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
}
... ...
... ... @@ -43,7 +43,9 @@ class RemainDay extends Command
1893,
2066,
2250,
2193
2193,
2399,
1685
];//需要单独处理的项目
/**
* The console command description.
... ...
... ... @@ -10,12 +10,15 @@
namespace App\Console\Commands\LyhTest;
use App\Helper\Common;
use App\Models\Blog\Blog;
use App\Models\Com\V6WeeklyReport;
use App\Models\News\News;
use App\Models\Product\Category;
use App\Models\Product\CategoryRelated;
use App\Models\Product\Product;
use App\Models\ProjectAssociation\ProjectAssociation;
use App\Models\RouteMap\RouteMap;
use App\Models\Template\BTemplate;
use App\Models\Visit\Visit;
use App\Models\Visit\VisitItem;
use App\Models\Workchat\MessagePush;
... ... @@ -43,16 +46,51 @@ class DownloadProject extends Command
public function handle(){
echo date('Y-m-d H:i:s') . 'start' . PHP_EOL;
ProjectServer::useProject(535);
$this->model = new Visit();
$data = $this->importVisit();
dd($data);
ProjectServer::useProject(671);
$this->newsImportBlog();
DB::disconnect('custom_mysql');
echo date('Y-m-d H:i:s') . 'end' . PHP_EOL;
return true;
}
/**
* @remark :新闻导入到博客
* @name :newsImportBlog
* @author :lyh
* @method :post
* @time :2025/6/7 11:12
*/
public function newsImportBlog(){
$newsModel = new News();
$newsLists = $newsModel->list(['status'=>1]);
$blogModel = new Blog();
foreach ($newsLists as $item){
$data = [
'name'=>$item['name'],
'status'=>1,
'remark'=>$item['remark'],
'text'=>$item['text'],
'image'=>$item['image'],
'url'=>$item['url'],
'seo_title'=>$item['seo_title'],
'seo_description'=>$item['seo_title'],
'seo_keywords'=>$item['seo_title'],
'project_id'=>671,
'release_at'=>$item['release_at'],
'og_image'=>$item['og_image']
];
$id = $blogModel->addReturnId($data);
RouteMap::delRoute(RouteMap::SOURCE_NEWS,$item['id'],671);
$route = RouteMap::setRoute($item['url'],RouteMap::SOURCE_BLOG,$id,671);
$blogModel->edit(['url'=>$route],['id'=>$id]);
//更新当前的可视化归宿
$templateModel = new BTemplate();
$templateModel->edit(['source'=>3,'source_id'=>$id],['source'=>4,'source_id'=>$item['id'],'is_custom'=>0,'is_list'=>0]);
}
return true;
}
/**
* @remark :导出明细
* @name :importVisit
* @author :lyh
... ...
... ... @@ -68,7 +68,9 @@ class UpdateRoute extends Command
*/
public function handle()
{
return $this->getAiBlog();
ProjectServer::useProject(1181);
return $this->setProductKeyword();
DB::disconnect('custom_mysql');
}
/**
... ... @@ -299,18 +301,18 @@ class UpdateRoute extends Command
if(!empty($lists)){
foreach ($lists as $v){
if(!empty($v['route'])){
// echo date('Y-m-d H:i:s') . 'route :'.$v['id'] . PHP_EOL;
// $route = $this->setRoute($v['route'], RouteMap::SOURCE_PRODUCT_KEYWORD, $v['id'], $v['project_id']);
// $keywordModel->edit(['route'=>$route],['id'=>$v['id']]);
echo date('Y-m-d H:i:s') . 'route :'.$v['id'] . PHP_EOL;
$route = $this->setRoute($v['title'], RouteMap::SOURCE_PRODUCT_KEYWORD, $v['id'], $v['project_id']);
$keywordModel->edit(['route'=>$route],['id'=>$v['id']]);
continue;
}else{
echo date('Y-m-d H:i:s') . 'route :'.$v['id'] . PHP_EOL;
$route = Translate::tran($v['title'], 'en');
if(!empty($route)){
echo date('Y-m-d H:i:s') . $route . PHP_EOL;
$route = $this->setRoute($route, RouteMap::SOURCE_PRODUCT_KEYWORD, $v['id'], $v['project_id']);
$keywordModel->edit(['route'=>$route],['id'=>$v['id']]);
}
// echo date('Y-m-d H:i:s') . 'route :'.$v['id'] . PHP_EOL;
// $route = Translate::tran($v['title'], 'en');
// if(!empty($route)){
// echo date('Y-m-d H:i:s') . $route . PHP_EOL;
// $route = $this->setRoute($route, RouteMap::SOURCE_PRODUCT_KEYWORD, $v['id'], $v['project_id']);
// $keywordModel->edit(['route'=>$route],['id'=>$v['id']]);
// }
}
}
}
... ... @@ -372,7 +374,7 @@ class UpdateRoute extends Command
}
}
$route = $sign.$suffix;
while(RouteMap::isExist($route, $source_id, $project_id)){
while(RouteMap::isExist($route, $source_id, $project_id,$source)){
$route = $sign .'-'.$i.$suffix;
$i++;
}
... ...
... ... @@ -10,6 +10,7 @@
namespace App\Console\Commands\LyhTest;
use App\Console\Commands\Domain\DomainInfo;
use App\Helper\OaGlobalsoApi;
use App\Http\Logic\Aside\Project\ProjectLogic;
use App\Models\Ai\AiBlog;
use App\Models\Ai\AiBlogAuthor;
... ... @@ -28,6 +29,7 @@ use App\Models\RouteMap\RouteMap;
use App\Models\Template\BTemplateMain;
use App\Models\Template\TemplateTypeMain;
use App\Models\Visit\Visit;
use App\Models\WebSetting\TranslateBigProject;
use App\Models\WebSetting\WebLanguage;
use App\Models\WebSetting\WebSetting;
use App\Models\Workchat\MessagePush;
... ... @@ -57,18 +59,27 @@ class lyhDemo extends Command
public function handle(){
$projectModel = new Project();
$lists = $projectModel->list(['delete_status' => 0,'project_type'=>0,'extend_type'=>0,'type'=>['in',[1,2,3,4,6]]], 'id', ['id']);
$buildModel = new DeployBuild();
$lists = $projectModel->list(['delete_status' => 0,'project_type'=>0,'is_upgrade'=>0,'id'=>['<',1659],'extend_type'=>0,'type'=>['in',[1,2,3,4,6]]], 'id', ['id','notice_order_id']);
foreach ($lists as $item){
// echo date('Y-m-d H:i:s') . '开始--项目的id:'. $item['id'] . PHP_EOL;
ProjectServer::useProject($item['id']);
$webSettingModel = new WebSetting();
$info = $webSettingModel->read(['project_id'=>$item['id']]);
if($info === false){
$webSettingModel->addReturnId(['project_id'=>$item['id']]);
echo '当前数据为空:'.$item['id'].PHP_EOL;
echo date('Y-m-d H:i:s') . '开始--项目的id:'. $item['id'] . PHP_EOL;
try {
if(!empty($item['notice_order_id'])){
$api = new OaGlobalsoApi();
$data = $api->order_info($item['notice_order_id']);
if(!empty($data)){
if(isset($data['data']['ads_price'])){
echo '奖励金额:'.$data['data']['ads_price'].PHP_EOL;
$buildModel->edit(['ads_price'=>$data['data']['ads_price'] ?? 0],['project_id'=>$item['id']]);
}
}
}
}catch (\Exception $e){
continue;
}
DB::disconnect('custom_mysql');
}
echo date('Y-m-d H:i:s') . '结束。。。' . PHP_EOL;
}
public function _actionTemplateMain(){
... ...
... ... @@ -188,6 +188,7 @@ class Supervisory extends Command
->where('gl_project.type', Project::TYPE_TWO)
->where('gl_project.extend_type', 0) // 是否续费是由extend_type字段控制
->where('gl_project.delete_status', Project::IS_DEL_FALSE)
->where('gl_project.project_type', '!=', Project::PROJECT_TYPE_SEO)//排除白帽项目
->where(function ($subQuery) {
$subQuery->orwhere('c.qa_status', OnlineCheck::STATUS_ONLINE_TRUE)->orwhere('gl_project.is_upgrade', Project::IS_UPGRADE_TRUE);
})
... ...
... ... @@ -75,6 +75,7 @@ class CopyProject extends Command
}catch (\Exception $e){
echo $e->getMessage().PHP_EOL;
echo '复制数据库失败:'.$old_project_id . '<->'.$project_id;
continue;
}
//修改项目状态
$projectModel->edit(['delete_status'=>0],['id'=>$project_id]);
... ...
<?php
/**
* @remark :
* @name :InitKeywordComment.php
* @author :lyh
* @method :post
* @time :2025/6/3 15:38
*/
namespace App\Console\Commands\Project;
use App\Helper\Common;
use App\Helper\Gpt;
use App\Models\Ai\AiCommand;
use App\Models\Com\NoticeLog;
use App\Models\Project\AggregateKeywordComment;
use App\Models\Project\Project;
use Illuminate\Console\Command;
class InitKeywordComment extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'init_keyword_comment';
/**
* The console command description.
*
* @var string
*/
protected $description = '初始化关键字评论';
public $number = 100;
public function handle(){
return $this->_action(467);
while (true){
$list = NoticeLog::where('type', NoticeLog::TYPE_INIT_KEYWORD_COMMON)->where('status', NoticeLog::STATUS_PENDING)->get();
if(empty($list)){
sleep(200);
continue;
}
foreach ($list as $item){
echo date('Y-m-d H:i:s').'start:' . $item['id'] . PHP_EOL;
$project_id = $item['data']['project_id'];
echo date('Y-m-d H:i:s').'执行的项目id:' . $project_id . PHP_EOL;
try {
$this->_action($project_id);
$count = $keywordCommonModel->counts(['project_id'=>$project_id]);
if($count > 100){
$item->status = NoticeLog::STATUS_SUCCESS;
$item->save();
}
}catch (\Exception $e){
echo date('Y-m-d H:i:s').'错误信息:'.$e->getMessage().PHP_EOL;
continue;
}
echo date('Y-m-d H:i:s').'end:' . $item['id'] . PHP_EOL;
}
}
return true;
}
/**
* @remark :执行的方法
* @name :_action
* @author :lyh
* @method :post
* @time :2025/6/3 15:42
*/
public function _action($project_id){
$aiCommonModel = new AiCommand();
$info = $aiCommonModel->read(['key'=>'tag_comment']);
$text = Gpt::instance()->openai_chat_qqs($info['ai']);
$text = Common::deal_keywords($text);
preg_match_all('/\{[^{}]*\}/', $text, $matches);
if(!empty($text)){
$data = [];
foreach ($matches[0] as $item){
$item = str_replace("'", '"', $item);
// 解码成 PHP 关联数组
$item = json_decode($item, true);
if(!isset($item['name']) || !isset($item['comment'])){
continue;
}
$data[] = [
'nickname'=>$item['name'],
'text'=>$item['comment'],
'project_id'=>$project_id,
'type'=>1,
'uid'=>0,
'created_at'=>date('Y-m-d H:i:s'),
'updated_at'=>date('Y-m-d H:i:s')
];
}
$keywordCommonModel = new AggregateKeywordComment();
$keywordCommonModel->insertAll($data);
}
return true;
}
}
... ...
... ... @@ -227,7 +227,8 @@ class SyncProject extends Command
'version'=>$version
],
'deploy_build' => [
'login_mobile'=>$param['principal_mobile']
'login_mobile'=>$param['principal_mobile'],
'ads_price'=>$param['ads_price'] ?? 0
],
'deploy_optimize' => [
// 'api_no' => 0
... ...
<?php
namespace App\Console\Commands\Tdk;
use App\Exceptions\ValidateException;
use App\Helper\Arr;
use App\Helper\Gpt;
use App\Models\Ai\AiCommand;
use App\Models\Com\NoticeLog;
use App\Models\Domain\DomainInfo;
use App\Models\Product\Keyword;
use App\Models\Project\AggregateKeywordAffix;
use App\Models\Project\DeployBuild;
use App\Models\Project\DeployOptimize;
use App\Models\Project\ProjectKeywordAiTask;
use App\Services\ProjectServer;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
/**
* 关键词聚合页AI生成内容
* Class InitProject
* @package App\Console\Commands
* @author zbj
* @date 2025/06/06
*/
class KeywordPageAiContent extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'keyword_page_ai_content';
/**
* The console command description.
*
* @var string
*/
protected $description = '关键词聚合页AI生成内容';
/**
* 统计图表类型 随机一个
* @var string[]
*/
protected $chart_types = [
'柱状图',
'折线图',
];
/**
* @return bool
*/
public function handle()
{
while (true) {
$task = ProjectKeywordAiTask::getPendingTask();
if (!$task) {
sleep(10);
continue;
}
$project_id = $task->project_id;
echo getmypid() . ' ' . date('Y-m-d H:i:s') . ' start project_id: ' . $project_id . PHP_EOL;
try {
ProjectServer::useProject($project_id);
$update_rows = $this->ai_content($task);
DB::disconnect('custom_mysql');
ProjectKeywordAiTask::finish($task->id, $update_rows);
// $update_rows && $this->sendNotify($project_id);
} catch (ValidateException $e) {
echo getmypid() . ' ' . date('Y-m-d H:i:s') . 'line: ' . $e->getLine() . ' error: ' . $project_id . '->' . $e->getMessage() . PHP_EOL;
$task->status = ProjectKeywordAiTask::STATUS_FAIL;
$task->remark = mb_substr($e->getMessage(), 0, 250);
$task->save();
} catch (\Exception $e) {
echo getmypid() . ' ' . date('Y-m-d H:i:s') . 'line: ' . $e->getLine() . ' error: ' . $project_id . '->' . $e->getMessage() . PHP_EOL;
ProjectKeywordAiTask::retry($task->id, $e->getMessage());
}
echo getmypid() . ' ' . date('Y-m-d H:i:s') . ' end project_id: ' . $project_id . PHP_EOL;
}
}
/**
* @param ProjectKeywordAiTask $task
* @author zbj
* @date 2025/6/6
*/
public function ai_content(ProjectKeywordAiTask $task)
{
//前后缀
$affix = AggregateKeywordAffix::where('project_id', $task->project_id)->first();
$prefix = empty($affix['prefix']) ? [] : explode("\r\n", $affix['prefix']);
$suffix = empty($affix['suffix']) ? [] : explode("\r\n", $affix['suffix']);
if (!$prefix || !$suffix) {
throw new ValidateException('扩展标题前后缀不存在');
}
//公司英文描述
$company_en_description = DeployOptimize::where('project_id', $task->project_id)->value('company_en_description');
if (!$company_en_description) {
throw new ValidateException('公司英文描述不存在');
}
//指令
$ai_commands = AiCommand::whereIn('key', ['tag_sale_content', 'tag_count_content', 'tag_data_table'])->where('project_id', 0)->select('key', 'scene', 'ai')->get()->toArray();
$project_ai_commands = AiCommand::whereIn('key', ['tag_sale_content', 'tag_count_content', 'tag_data_table'])->where('project_id', $task->project_id)->select('key', 'scene', 'ai')->get()->toArray();
$ai_commands = Arr::setValueToKey($ai_commands, 'key');
$project_ai_commands = Arr::setValueToKey($project_ai_commands, 'key');
foreach ($ai_commands as $k => $ai_command) {
if (!empty($project_ai_commands[$k])) {
$ai_commands[$k] = $project_ai_commands[$k];
}
}
//没有标题、文案、图表的关键词
$keyword_ids = Keyword::whereNull('sale_title')->orWhereNull('sale_content')->orWhereNull('table_html')
->orWhereNull('count_title')->orWhereNull('count_html')
->pluck('id')
->toArray();
$update_rows = 0;
foreach ($keyword_ids as $id) {
//缓存 在处理的项目数据 id
$cache_key = "keyword_page_ai_content_task_lock_{$task->project_id}_{$id}";
if (!Redis::setnx($cache_key, 1)) {
continue;
}
Redis::expire($cache_key, 120);
$keyword = Keyword::where('id', $id)->select(['id', 'title', 'sale_title', 'sale_content', 'table_html', 'count_title', 'count_html'])->first();
echo getmypid() . ' ' . date('Y-m-d H:i:s') . ' id:' . $keyword['id'] . ' project_id:' . $task->project_id . PHP_EOL;
$update = false;
if (empty($keyword['sale_title'])) {
$sale_title = $this->new_title($keyword['title'], $prefix, $suffix);
$keyword->sale_title = $sale_title;
$update = true;
}
if (empty($keyword['sale_content']) && $keyword->sale_title) {
$content = $this->ai_send($keyword->sale_title, $company_en_description, $ai_commands['tag_sale_content']['ai']);
if ($content) {
$keyword->sale_content = $content;
$update = true;
}
}
if (empty($keyword['table_html']) && $keyword->sale_title) {
$content = $this->ai_send($keyword->sale_title, $company_en_description, $ai_commands['tag_data_table']['ai']);
if ($content) {
$keyword->table_html = str_replace(['```html', '``html', '```'], '', $content);
$update = true;
}
}
if (empty($keyword['count_title'])) {
$count_title = $this->new_title($keyword['title'], $prefix, $suffix);
$count_title && $keyword->count_title = $count_title;
$update = true;
}
if (empty($keyword['count_html']) && $keyword->sale_title) {
$content = $this->ai_send($keyword->sale_title, $company_en_description, $ai_commands['tag_count_content']['ai']);
if ($content) {
$keyword->count_html = $this->fixChart(str_replace(['```html', '``html', '```'], '', $content));
$update = true;
}
}
if ($update) {
$keyword->save();
$update_rows++;
}
}
return $update_rows;
}
public function new_title($title, $prefix, $suffix)
{
//打乱顺序
shuffle($prefix);
shuffle($suffix);
//标题(title):{聚合页扩展标题前缀} keywords {聚合页扩展标题后缀} {聚合页扩展标题后缀}
return sprintf('%s %s %s %s', $prefix[0], $title, $suffix[0], $suffix[1]);
}
public function ai_send($title, $company_description, $prompt)
{
if (strpos($prompt, '{title}') !== false) {
$prompt = str_replace('{title}', $title, $prompt);
}
if (strpos($prompt, '{company introduction}') !== false) {
$prompt = str_replace('{company introduction}', $company_description, $prompt);
}
if (strpos($prompt, '{chart_type}') !== false) {
shuffle($this->chart_types);
$prompt = str_replace('{chart_type}', $this->chart_types[0], $prompt);
}
$text = Gpt::instance()->openai_chat_qqs($prompt);
if (!$text) {
echo getmypid() . ' ' . '生成失败' . PHP_EOL;
}
return $text;
}
function fixChart($html)
{
$html = '<body>' . $html . '</body>';
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$canvas_count = $dom->getElementsByTagName('canvas')->count();
//没有canvas
if (!$canvas_count) {
$div = $dom->getElementsByTagName('div');
foreach ($div as $element) {
if ($element->hasAttribute('id')) {
$canvas = $dom->createElement('canvas');
$canvas->setAttribute('id', $element->getAttribute('id'));
$element->removeAttribute('id');
$element->appendChild($canvas);
break;
}
}
}
$body = $dom->getElementsByTagName('body')->item(0);
$modifiedHtml = '';
foreach ($body->childNodes as $child) {
$modifiedHtml .= $dom->saveHTML($child);
}
return $modifiedHtml;
}
public function sendNotify($project_id)
{
//获取当前项目的域名
$domainModel = new DomainInfo();
$domainInfo = $domainModel->read(['project_id' => $project_id]);
if ($domainInfo === false) {
//获取测试域名
$deployBuildModel = new DeployBuild();
$buildInfo = $deployBuildModel->read(['project_id' => $project_id]);
$domain = $buildInfo['test_domain'];
} else {
$domain = 'https://' . $domainInfo['domain'] . '/';
}
$url = $domain . 'api/update_page/';
$param = [
'project_id' => $project_id,
'type' => 1,
'route' => 2,
'url' => [],
'language' => [],
];
NoticeLog::createLog(NoticeLog::GENERATE_PAGE, json_encode(['c_url' => $url, 'c_params' => $param]), date('Y-m-d H:i:s', time() + 300));
echo getmypid() . ' ' . '更新中请稍后, 更新完成将会发送站内信通知更新结果!' . PHP_EOL;
}
}
... ...
... ... @@ -5,6 +5,7 @@ namespace App\Console\Commands\Test;
use App\Helper\Arr;
use App\Http\Logic\Bside\Product\CategoryLogic;
use App\Models\Com\Notify;
use App\Models\CustomModule\CustomModuleContent;
use App\Models\Devops\ServerConfig;
use App\Models\Devops\ServersIp;
use App\Models\Domain\DomainCreateTask;
... ... @@ -16,6 +17,7 @@ use App\Models\Product\Product;
use App\Models\Project\DeployBuild;
use App\Models\Project\DeployOptimize;
use App\Models\Project\Project;
use App\Models\RouteMap\RouteMap;
use App\Models\Template\BCustomTemplate;
use App\Models\WebSetting\WebLanguage;
use App\Services\BatchExportService;
... ... @@ -41,7 +43,197 @@ class Temp extends Command
public function handle()
{
$this->specialImport();
}
/**
* 3531项目导入扩展模块内容
* @return bool
* @author Akun
* @date 2025/06/05 10:47
*/
public function specialImport()
{
$file_url = 'https://ecdn6.globalso.com/upload/p/3531/file/2025-06/news.csv';
$domain = 'www.hybio.com.cn';
$project_id = 3531;
$is_gbk = 0;
$file_code_type = $this->get_code_type($file_url);
if ($file_code_type === false) {
echo 'date:' . date('Y-m-d H:i:s') . ', import fail, error: 文件编码格式错误' . PHP_EOL;
return true;
} elseif ($file_code_type === 'GBK') {
$is_gbk = 1;
setlocale(LC_ALL, 'zh_CN');
}
//读取csv文件
$line_of_text = [];
try {
$opts = [
'http' => [
'method' => 'GET',
'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'
],
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false
]
];
$file_handle = fopen($file_url, 'r', null, stream_context_create($opts));
while (!feof($file_handle)) {
$line_of_text[] = fgetcsv($file_handle, 0, ',');
}
fclose($file_handle);
} catch (\Exception $e) {
$this->output('import fail, error: ' . $e->getMessage());
return true;
}
$total_count = 0; //总条数
$success_count = 0; //成功导入条数
$repeat_count = 0; //过滤已存在条数
$fail_count = 0;//导入失败条数
$fail_line = [];//失败行数
if (count($line_of_text) > 1) {
//设置数据库
$project = ProjectServer::useProject($project_id);
if ($project) {
foreach ($line_of_text as $k => $v) {
if ($k > 0 && isset($v[0]) && $v[0]) {
if ($is_gbk) {
foreach ($v as $kk => $vv) {
$v[$kk] = mb_convert_encoding($vv, 'utf-8', 'gbk');
}
}
$v[0] = $this->special2str($v[0]);
$total_count += 1;
try {
if ($this->importModule($project_id, $domain, $v)) {
$success_count += 1;
} else {
$repeat_count += 1;
}
} catch (\Exception $e) {
$fail_count += 1;
$fail_line[] = $k + 1;
$this->output('title: ' . $v[0] . ', import fail, error: ' . $e->getMessage());
}
}
}
}
//关闭数据库
DB::disconnect('custom_mysql');
}
$this->output('import end, total count: ' . $total_count . ', success count: ' . $success_count . ', repeat_count count: ' . $repeat_count . ', fail_count count: ' . $fail_count);
return true;
}
protected function importModule($project_id, $domain, $data)
{
$model = new CustomModuleContent();
$module = $model->read(['name' => $data[0]]);
if (!$module) {
$content = '';
if ($data[4] ?? '') {
//处理内容中的图片
preg_match_all('/<img\s+[^>]*?src\s*=\s*(\'|\")(.*?)\\1[^>]*?\/?\s*>/i', $data[4], $result);
if ($result[2] ?? []) {
foreach ($result[2] as $img) {
$new_img = check_remote_url_down($img, $project_id, $domain, 1);
$new_img && $data[4] = str_replace($img, $new_img, $data[4]);
}
}
//处理内容中的视频
preg_match_all('/<source\s+[^>]*?src\s*=\s*(\'|\")(.*?)\\1[^>]*?\/?\s*>/i', $data[4], $result_video);
if ($result_video[2] ?? []) {
foreach ($result_video[2] as $video) {
$new_video = check_remote_url_down($video, $project_id, $domain, 1);
$new_video && $data[4] = str_replace($video, $new_video, $data[4]);
}
}
$content = $data[4];
}
$seo_title = '';
if ($data[6] ?? '') {
$seo_title = substr(strip_tags($data[6]), 0, 70);
}
$seo_keywords = '';
if ($data[7] ?? '') {
$seo_keywords = substr(strip_tags(str_replace('^v6sp$', ',', $data[7])), 0, 255);
}
$seo_description = '';
if ($data[8] ?? '') {
$seo_description = substr(strip_tags($data[8]), 0, 200);
}
$release_at = date('Y-m-d H:i:s');
if ($data[9] ?? '') {
$release_at = date('Y-m-d H:i:s', strtotime($data[9]));
}
$id = $model->addReturnId(
[
'name' => $data[0],
'category_id' => ',1,',
'module_id' => 2,
'content' => $content,
'seo_title' => $seo_title,
'seo_keywords' => $seo_keywords,
'seo_description' => $seo_description,
'project_id' => $project_id,
'operator_id' => 8143,
'status' => 0,
'route' => '',
'release_at' => $release_at
]
);
$route = RouteMap::setRoute($data[0], RouteMap::SOURCE_MODULE, $id, $project_id);
$model->edit(['route' => $route], ['id' => $id]);
return true;
}
return false;
}
//特殊字符转换
protected function special2str($str)
{
if (strpos($str, ';') === false) {
return $str;
}
$list = [
'&lt;' => '<',
'&gt;' => '>',
'&amp;' => '&',
'&acute;' => "'",
'&quot;' => '"',
'&nbsp;' => ' ',
'&#x27;' => "'"
];
foreach ($list as $k => $v) {
$str = str_replace($k, $v, $str);
}
return $str;
}
/**
... ...
<?php
namespace App\Console\Commands;
use App\Models\WorkOrder\WorkOrderLog;
use App\Services\DingTalkService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class WorkOrderDing extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'workorder:ding';
/**
* The console command description.
*
* @var string
*/
protected $description = '售后工单钉钉通知';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
while (true) {
try {
$log = WorkOrderLog::where('ding', 0)->first();
if (!$log) {
sleep(3);
continue;
}
$mobile = $log->manager->mobile;
$response = Http::withBasicAuth(
env('DINGDING_BASIC_USER'),
env('DINGDING_BASIC_PASS')
)->get('https://oa.cmer.com/api/dingding/user/' . $mobile);
if ($response->status() == 200) {
$userid = $response->json()['data']['userid'];
$text = "**您有新的售后工单**<br>";
$text .= "工单ID:{$log->work_order_id}<br>";
$text .= "工单类型:<font color='red'>{$log->workOrder->product}</font><br>";
$text .= "项目:{$log->workOrder->project->title}<br>";
$text .= "时间:{$log->created_at}<br>";
$ding = new DingTalkService();
$resp = $ding->danliao(json_encode([
'text' => $text,
'title' => '售后工单通知',
]), [$userid]);
$log->ding = 1;
}else
$log->ding = 2;
$log->save();
}catch (\Exception $exception){
echo date('Y-m-d H:i:s')." ".$exception->getMessage()."\n";
break;
}
}
}
}
... ...
<?php
namespace App\Exceptions;
use Exception;
/**
* @notes: 验证
* Class ValidateException
* @package App\Exceptions
*/
class ValidateException extends Exception
{
}
... ...
... ... @@ -52,6 +52,7 @@ class PrivateController extends BaseController
->leftJoin('gl_project_online_check as c', 'gl_project.id', '=', 'c.project_id')
->leftJoin('gl_domain_info as d', 'gl_project.id', '=', 'd.project_id')
->where('gl_project.type', Project::TYPE_TWO)
->where('gl_project.project_type',Project::TYPE_ZERO)
->where('gl_project.extend_type', 0) // 是否续费是由extend_type字段控制
->where('gl_project.delete_status', Project::IS_DEL_FALSE)
->where(function ($subQuery) {
... ...
... ... @@ -31,6 +31,19 @@ class HrController extends BaseController
}
/**
* @remark :获取列表数据
* @name :getManagerList
* @author :lyh
* @method :post
* @time :2025/6/7 9:22
*/
public function getManagerList(){
$manageHrModel = new ManageHr();
$lists = $manageHrModel->lists($this->map,$this->page,$this->row);
$this->response('success', Code::SUCCESS, $lists);
}
/**
* @remark :获取详情
* @name :info
* @author :lyh
... ...
... ... @@ -15,6 +15,7 @@ use App\Models\Channel\Channel;
use App\Models\Channel\User;
use App\Models\Channel\Zone;
use App\Models\Com\City;
use App\Models\Com\NoticeLog;
use App\Models\Com\UpdateLog;
use App\Models\Devops\ServerConfig;
use App\Models\Devops\ServersIp;
... ... @@ -960,14 +961,10 @@ class ProjectController extends BaseController
'id'=>'required',
'aicc'=>'required',
'hagro'=>'required',
// 'exclusive_aicc_day'=>'required',
// 'exclusive_hagro_day'=>'required',
],[
'id.required' => 'id不能为空',
'aicc.required' => 'aicc是否开启不能为空',
'hagro.required' => 'hagro是否开启不能为空',
// 'exclusive_aicc_day.required' => '服务天数不能为空',
// 'exclusive_hagro_day.required' => '服务天数不能为空',
]);
$logic->saveOtherProject();
$this->response('success');
... ... @@ -1136,7 +1133,7 @@ class ProjectController extends BaseController
//获取项目数据
$projectModel = new Project();
$projectInfo = $projectModel->read(['id'=>$this->param['id']],['project_type','serve_id','site_status']);
$projectInfo = $projectModel->read(['id'=>$this->param['id']],['project_type','serve_id','site_status','site_token']);
if(!$projectInfo){
$this->fail('获取项目数据失败');
}
... ... @@ -1144,61 +1141,75 @@ class ProjectController extends BaseController
$this->response('success');
}
//获取域名数据
$domainModel = new DomainInfoModel();
$domainInfo = $domainModel->read(['project_id'=>$this->param['id']],['id','domain','amp_status']);
if(!$domainInfo){
$this->fail('获取域名数据失败');
}
if($this->param['site_status'] == 1){
//关闭站点:通知C端
$re = curl_get('https://'.$domainInfo['domain'].'/api/stop_or_start_website');
if(isset($re['status']) && $re['status'] !== 200){
$this->fail($re['message']);
if($projectInfo['serve_id'] == 8){
//自建站项目
if($this->param['site_status'] == 1){
//关闭站点
$site_token = $projectInfo['site_token'] ? $projectInfo['site_token'].'_expired' : '';
}else{
//开启站点
$site_token = str_replace('_expired','',$projectInfo['site_token']);
}
$projectModel->edit(['site_status'=>$this->param['site_status'],'site_token'=>$site_token],['id'=>$this->param['id']]);
}else{
//开启站点:创建建站任务
$serverIpModel = new ServersIp();
$serversIpInfo = $serverIpModel->read(['id' => $projectInfo['serve_id']], ['servers_id']);
if(!$serversIpInfo){
$this->fail('获取项目所属服务器失败');
//普通项目
//获取域名数据
$domainModel = new DomainInfoModel();
$domainInfo = $domainModel->read(['project_id'=>$this->param['id']],['id','domain','amp_status']);
if(!$domainInfo){
$this->fail('获取域名数据失败');
}
if ($projectInfo['project_type'] == Project::PROJECT_TYPE_SEO) {
$type = DomainCreateTask::TYPE_BLOG;
} else {
$type = DomainCreateTask::TYPE_MAIN;
}
if($this->param['site_status'] == 1){
//关闭站点:通知C端
$re = curl_get('https://'.$domainInfo['domain'].'/api/stop_or_start_website');
if(isset($re['status']) && $re['status'] !== 200){
$this->fail($re['message']);
}
}else{
//开启站点:创建建站任务
$serverIpModel = new ServersIp();
$serversIpInfo = $serverIpModel->read(['id' => $projectInfo['serve_id']], ['servers_id']);
if(!$serversIpInfo){
$this->fail('获取项目所属服务器失败');
}
//创建更新站点证书任务
$domainCreateTaskModel = new DomainCreateTask();
$task_info = $domainCreateTaskModel->read(['type' => $type, 'domain_id' => $domainInfo['id'], 'is_open' => DomainCreateTask::IS_OPEN, 'status' => ['<', DomainCreateTask::STATUS_SUC]], ['id']);
if (!$task_info) {
$domainCreateTaskModel->add([
'server_id' => $serversIpInfo['servers_id'],
'project_id' => $this->param['id'],
'domain_id' => $domainInfo['id'],
'type' => $type,
'is_open' => DomainCreateTask::IS_OPEN
]);
}
if ($projectInfo['project_type'] == Project::PROJECT_TYPE_SEO) {
$type = DomainCreateTask::TYPE_BLOG;
} else {
$type = DomainCreateTask::TYPE_MAIN;
}
if($domainInfo['amp_status']){
$task_info_amp = $domainCreateTaskModel->read(['type' => DomainCreateTask::TYPE_AMP, 'domain_id' => $domainInfo['id'], 'is_open' => DomainCreateTask::IS_OPEN, 'status' => ['<', DomainCreateTask::STATUS_SUC]], ['id']);
if (!$task_info_amp) {
//创建更新站点证书任务
$domainCreateTaskModel = new DomainCreateTask();
$task_info = $domainCreateTaskModel->read(['type' => $type, 'domain_id' => $domainInfo['id'], 'is_open' => DomainCreateTask::IS_OPEN, 'status' => ['<', DomainCreateTask::STATUS_SUC]], ['id']);
if (!$task_info) {
$domainCreateTaskModel->add([
'server_id' => $serversIpInfo['servers_id'],
'project_id' => $this->param['id'],
'domain_id' => $domainInfo['id'],
'type' => DomainCreateTask::TYPE_AMP,
'type' => $type,
'is_open' => DomainCreateTask::IS_OPEN
]);
}
if($domainInfo['amp_status']){
$task_info_amp = $domainCreateTaskModel->read(['type' => DomainCreateTask::TYPE_AMP, 'domain_id' => $domainInfo['id'], 'is_open' => DomainCreateTask::IS_OPEN, 'status' => ['<', DomainCreateTask::STATUS_SUC]], ['id']);
if (!$task_info_amp) {
$domainCreateTaskModel->add([
'server_id' => $serversIpInfo['servers_id'],
'project_id' => $this->param['id'],
'domain_id' => $domainInfo['id'],
'type' => DomainCreateTask::TYPE_AMP,
'is_open' => DomainCreateTask::IS_OPEN
]);
}
}
}
}
$projectModel->edit(['site_status'=>$this->param['site_status']],['id'=>$this->param['id']]);
$projectModel->edit(['site_status'=>$this->param['site_status']],['id'=>$this->param['id']]);
}
$this->response('success');
}
... ... @@ -1215,4 +1226,25 @@ class ProjectController extends BaseController
$this->response('success', Code::SUCCESS, $lists);
}
/**
* @remark :生成关键词图表数据
* @name :generateCountCharts
* @author :lyh
* @method :post
* @time :2025/6/10 10:51
*/
public function generateCountCharts(){
$this->request->validate([
'project_id'=>'required',
],[
'project_id.required' => '项目id不能为空',
]);
$noticeModel = new NoticeLog();
$info = $noticeModel->read(['type'=>NoticeLog::TYPE_GENERATE_COUNT_CHARTS,'status'=>0,'data'=>['like','%"'.$this->param['project_id'].'"%']]);
if($info !== false){
$this->fail('当前数据在生成中');
}
NoticeLog::createLog(NoticeLog::TYPE_GENERATE_COUNT_CHARTS, ['project_id' => $this->param['project_id']]);
$this->response('success');
}
}
... ...
<?php
namespace App\Http\Controllers\Aside\WorkOrder;
use App\Enums\Common\Code;
use App\Http\Controllers\Aside\BaseController;
use App\Http\Requests\Aside\WorkOrder\WorkOrderListRequest;
use App\Http\Requests\Aside\WorkOrder\WorkOrderUpdateRequest;
use App\Models\WorkOrder\WorkOrder;
use App\Models\WorkOrder\WorkOrderLog;
use Illuminate\Support\Facades\DB;
class WorkOrderController extends BaseController
{
/**
* A端工单列表
* 显示有我参与的工单列表
*/
public function index(WorkOrderListRequest $request)
{
/*
* A端工程师工单列表, 查询我的工单
*/
$request->validated();
# manage_id 或 engineer_id 是我
$lists = WorkOrderLog::with([
'workOrder.logs.manager:id,name',
'workOrder.project:id,company,title',
])
->where('manage_id', $this->manage['id'])
->when($request->input('project_id') !== null, function ($query) use ($request) {
// project_id 查 workOrder
return $query->whereHas('workOrder', function ($q) use ($request) {
$q->where('project_id', $request->input('project_id'));
});
})
->when($request->input('status') !== null, function ($query) use ($request) {
// status 查 workOrder
return $query->whereHas('workOrder', function ($q) use ($request) {
$q->where('status', $request->input('status'));
});
})
->when($request->input('search'), function ($query) use ($request) {
// search 查 workOrder
return $query->whereHas('workOrder', function ($q) use ($request) {
$q->where('product', 'like', '%' . $request->input('search') . '%')
->orWhere('content', 'like', '%' . $request->input('search') . '%');
});
})
->orderBy('id', 'desc')
->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
/**
* @param WorkOrderListRequest $request
* @return void
* A端管理员的工单列表
*/
public function manager(WorkOrderListRequest $request)
{
$request->validated();
// 管理员查看所有工单
$lists = WorkOrder::with([
'user:id,name',
'manager:id,name',
'engineer:id,name',
'logs.manager:id,name',
'project:id,company:title',
])
->when($request->input('project_id') !== null, function ($query) use ($request) {
return $query->where('project_id', $request->input('project_id'));
})
->when($request->input('status') !== null, function ($query) use ($request) {
return $query->where('status', $request->input('status'));
})
->when($request->input('search'), function ($query) use ($request) {
return $query->where('product', 'like', '%' . $request->input('search') . '%')
->orWhere('content', 'like', '%' . $request->input('search') . '%');
})
->orderBy('id', 'desc')
->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
/**
* A端工单详情
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$workOrder = WorkOrder::with([
'logs.manager:id,name',
'user:id,name',
'manager:id,name',
'engineer:id,name',
'project:id,company:title',
])->find($id);
if (!$workOrder)
$this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
// TODO 判断是否有查看工单详情权限,待添加
$this->response('success', Code::SUCCESS, $workOrder->toArray());
}
/**
* A端操作工单,工程师操作的是工单日志
* - 工程师:回复工单,自动��拆分给自己的子任务改为完成
* - 工单第一对接人:
* - 邀请工程师处理工单
* - 修改工单状态
* 若全部子任务完成,则将工单状态改为完成
*/
public function update(WorkOrderUpdateRequest $request, $id)
{
$request->validated();
$log = WorkOrderLog::find($id); // 拆分的子工单
if (!$log) {
$this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
}
if ($log->manage_id != $this->manage['id']) {
// 只能操作自己的工单
$this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
}
$workOrder = $log->workOrder;
$result = DB::transaction(function () use ($request, $workOrder, $log) {
if ($request->input('engineer_ids'))
{
// 有邀请工程师协同处理
foreach ($request->input('engineer_ids') as $engineer_id)
{
try {
// 利用唯一索引去重
$new_log = new WorkOrderLog();
$new_log->manage_id = $engineer_id;
$workOrder->logs()->save($new_log);
$workOrder->engineer_id = $engineer_id;
$workOrder->save();
}catch (\Exception $exception){}
}
}
if ($request->input('content'))
$log->content = $request->input('content');
if ($request->input('files'))
$log->files = $request->input('files');
if ($request->input('status') !== null)
{
$log->status = $request->input('status');
if ($log->status >= WorkOrder::STATUS_COMPLETED)
{
// 我的工单标记为已完成
$log->status = $request->input('status');
$log->end_at = now();
}
}
$log->save();
// 是否有未完成的子任务
$pending = $workOrder->logs()
->where('status', '<', WorkOrderLog::STATUS_COMPLETED)
->count();
if ($pending)
{
$workOrder->status = WorkOrder::STATUS_PROCESSING;
}else
{
$workOrder->status = WorkOrder::STATUS_COMPLETED;
// 如果所有子任务都完成了,则将工单状态改为已完成
$workOrder->end_at = now();
}
$workOrder->save();
return $log;
});
$this->response('success', Code::SUCCESS, $result->toArray());
}
}
... ...
... ... @@ -143,9 +143,15 @@ class AyrShareController extends BaseController
$data = [
'profileKey'=>$info['profile_key']
];
$res = $ayrShareHelper->post_generate_jwt($data);
if($res['status'] == 'fail'){
$this->response($res['message'],Code::USER_ERROR);
try {
$res = $ayrShareHelper->post_generate_jwt($data);
if($res['status'] == 'fail'){
$ayrShareLogic->ayr_share_edit(['profile_key'=>null,'bind_platforms'=>null,'ref_id'=>null],$info['id']);
$this->response('当前key值已过期,请刷新重新绑定',Code::USER_ERROR);
}
}catch (\Exception $e){
$ayrShareLogic->ayr_share_edit(['profile_key'=>null,'bind_platforms'=>null,'ref_id'=>null],$info['id']);
$this->response('当前key值已过期,请刷新重新绑定',Code::USER_ERROR);
}
$this->response('success',Code::SUCCESS,$res);
}
... ...
... ... @@ -34,6 +34,7 @@ use App\Models\Project\Country as CountryModel;
use App\Models\Project\Project;
use App\Models\RouteMap\RouteMap;
use App\Models\WebSetting\SettingNum;
use App\Models\WebSetting\TranslateBigProject;
use App\Models\WebSetting\WebLanguage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
... ... @@ -61,7 +62,8 @@ class CNoticeController extends BaseController
],[
'language.required' => 'language不能为空',
]);
$project_id_arr = explode(',',env('PROJECT_ID')) ?? [];
$bigProjectModel = new TranslateBigProject();
$project_id_arr = $bigProjectModel->selectField(['status'=>1],'project_id') ?? [];
if(in_array($this->user['project_id'],$project_id_arr)){
$this->response('success');
}
... ...
... ... @@ -3,13 +3,17 @@
namespace App\Http\Controllers\Bside\Product;
use App\Enums\Common\Code;
use App\Helper\Common;
use App\Helper\Gpt;
use App\Http\Controllers\Bside\BaseController;
use App\Http\Logic\Bside\Product\KeywordLogic;
use App\Http\Requests\Bside\Product\KeywordRequest;
use App\Models\Ai\AiCommand;
use App\Models\Product\Keyword;
use App\Models\Product\KeywordPage;
use App\Models\Product\KeywordRelated;
use App\Models\Product\Product;
use App\Models\Project\AggregateKeywordComment;
use App\Rules\Ids;
use Illuminate\Http\Request;
... ... @@ -312,4 +316,73 @@ class KeywordController extends BaseController
$logic->delAllRelated($this->param['keyword_id']);
$this->response('success');
}
/**
* @remark :添加评论
* @name :saveComment
* @author :lyh
* @method :post
* @time :2025/6/9 14:27
*/
public function saveComment(KeywordLogic $logic){
$this->request->validate([
'text'=>'required',
'nickname'=>'required',
'start_time'=>'required',
],[
'text.required' => '评论内容不能为空',
'nickname.required'=>'昵称不能为空',
'start_time.required'=>'发布时间不能为空',
]);
$data = $logic->saveComment();
$this->response('success',Code::SUCCESS,$data);
}
/**
* @remark :生成评论
* @name :sendComment
* @author :lyh
* @method :post
* @time :2025/6/9 11:10
*/
public function sendComment(KeywordLogic $logic){
$this->request->validate([
'count' => 'required|integer|max:10',
], [
'count.required' => '生成条数不能为空',
'count.integer' => '生成条数必须是整数',
'count.max' => '生成条数最大不能超过10',
]);
$data = $logic->sendComment();
$this->response('success',Code::SUCCESS,$data);
}
/**
* @remark :获取评论
* @name :getComment
* @author :lyh
* @method :post
* @time :2025/6/9 11:45
*/
public function getComment(KeywordLogic $logic){
$data = $logic->getComment($this->map,$this->page,$this->row);
$this->response('success',Code::SUCCESS,$data);
}
/**
* @remark :删除评论
* @name :getComment
* @author :lyh
* @method :post
* @time :2025/6/9 11:45
*/
public function delComment(KeywordLogic $logic){
$this->request->validate([
'id'=>'required',
],[
'id.required' => '主键不能为空',
]);
$data = $logic->delComment();
$this->response('success',Code::SUCCESS,$data);
}
}
... ...
... ... @@ -23,7 +23,7 @@ class WebSettingImageController extends BaseController
* @time :2023/9/21 15:12
*/
public function lists(WebSettingImage $webSettingImage){
$list = $webSettingImage->list($this->map,'id',['id','image','type']);
$list = $webSettingImage->list($this->map,'id',['id','image','type','is_call']);
foreach ($list as $k=>$v){
$v['image'] = getImageUrl($v['image'],$this->user['storage_type'],$this->user['project_location']);
$list[$k] = $v;
... ... @@ -40,11 +40,15 @@ class WebSettingImageController extends BaseController
*/
public function save(WebSettingImage $webSettingImage){
try {
$webSettingImage->del(['project_id'=>$this->user['project_id']]);
foreach ($this->param['data'] as $v){
$v['project_id'] = $this->user['project_id'];
$v['image'] = str_replace_url($v['image']);
$webSettingImage->add($v);
if(isset($v['id']) && !empty($v['id'])){
$v['image'] = str_replace_url($v['image']);
$webSettingImage->edit($v,['id'=>$v['id']]);
}else{
$v['project_id'] = $this->user['project_id'];
$v['image'] = str_replace_url($v['image']);
$webSettingImage->add($v);
}
}
}catch (\Exception $e){
$this->response('系统错误请联系管理员');
... ...
<?php
namespace App\Http\Controllers\Bside\WorkOrder;
use App\Enums\Common\Code;
use App\Http\Controllers\Bside\BaseController;
use App\Http\Requests\Aside\WorkOrder\WorkOrderListRequest;
use App\Http\Requests\Bside\WorkOrder\WorkOrderCreateRequest;
use App\Http\Requests\Bside\WorkOrder\WorkOrderUpdateRequest;
use App\Models\Project\Project;
use App\Models\WorkOrder\WorkOrder;
use App\Models\WorkOrder\WorkOrderLog;
use Illuminate\Support\Facades\DB;
class WorkOrderController extends BaseController
{
/**
* B端售后工单列表
*
* @return \Illuminate\Http\Response
*/
public function index(WorkOrderListRequest $request)
{
$request->validated();
$where = [
'project_id' => $this->user['project_id'],
];
// 分页查询
$lists = WorkOrder::with([
'manager:id,name',
])
->where($where)
->when($request->input('status'), function ($query) use ($request) {
return $query->where('status', $request->input('status'));
})
->when($request->input('search'), function ($query) use ($request) {
return $query->where('product', 'like', '%' . $request->input('search') . '%')
->orWhere('content', 'like', '%' . $request->input('search') . '%');
})
->orderBy('id', 'desc')->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
/**
* B端用户提交工单
*
* @return \Illuminate\Http\Response
*/
public function store(WorkOrderCreateRequest $request)
{
$request->validated();
$result = DB::transaction(function () use ($request) {
$workOrder = new WorkOrder;
$workOrder->product = $request->input('product');
$workOrder->content = $request->input('content');
$workOrder->files = json_encode($request->input('files'));
$workOrder->project_user_id = $this->user['id'];
$workOrder->project_id = $this->user['project_id'];
$workOrder->engineer_id = 0;
if ($this->project['type'] == 3){
// 项目类型是优化推广,项目负责人找优化
$seo = Project::find($this->user['project_id'])->deploy_optimize;
$workOrder->manage_id = $seo->manager_mid ?? $seo->optimist_mid ?? 0;
}else{
// 非优化推广项目,项目负责人找技术组长
$build = Project::find($this->user['project_id'])->deploy_build;
$workOrder->manage_id = $build->leader_mid ?? 0;
}
$workOrder->save();
// 添加工单日志
$log = new WorkOrderLog();
$log->manage_id = $workOrder->manage_id;
$workOrder->logs()->save($log);
return $workOrder;
});
$this->response('success', Code::SUCCESS, $result->toArray());
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$workOrder = WorkOrder::with([
'logs.manager:id,name',
'user:id,name',
'manager:id,name',
'engineer:id,name',
])->find($id);
if (!$workOrder) {
$this->response('工单未找到', 404);
}
if ($workOrder->project_id != $this->user['project_id'] && $workOrder->engineer_id != $this->user['id']) {
$this->response('无权限查看该工单', 403);
}
$this->response('success', Code::SUCCESS, $workOrder->toArray());
}
/**
* B端用户修改工单,只能是修改工单状态
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(WorkOrderUpdateRequest $request, $id)
{
$request->validated();
$workOrder = WorkOrder::find($id);
if (!$workOrder) {
$this->response('工单未找到', 404);
}
// b端只有自己项目下的账号可以更新工单
if ($workOrder->project_id != $this->user['project_id']) {
$this->response('无权限更新该工单', 403);
}
// 更新工单状态
$workOrder->status = WorkOrder::STATUS_COMPLETED;
$workOrder->end_at = now();
$workOrder->save();
// B端完成工单,将所有未完成的子工单状态改为已完成。并记录完成时间
$workOrder->logs()->where('status', '<', WorkOrderLog::STATUS_COMPLETED)
->update(['status' => WorkOrderLog::STATUS_COMPLETED, 'end_at' => now()]);
$this->response('success', Code::SUCCESS);
}
public function destroy($id)
{
$workOrder = WorkOrder::find($id);
if (!$workOrder) {
$this->response('工单未找到', 404);
}
if ($workOrder->status >= WorkOrder::STATUS_COMPLETED)
$this->response('已完结的工单不能删除', 403);
if ($this->user['type'] != 1 && $workOrder->project_user_id != $this->user['id']) {
// 只有项目负责人可以删除工单
$this->response('无权限删除该工单', 403);
}
# 删除工单
$workOrder->delete();
$this->response('工单已删除', Code::SUCCESS);
}
}
... ...
... ... @@ -514,22 +514,22 @@ class ImageController extends Controller
foreach ($files as $file){
if(isset($this->cache['image_max']) && ($this->cache['image_max'] != 0)){
if ($file->getSize() > $this->cache['image_max'] * 1024) {
$this->response('图片最大为'.$this->cache['image_max'],Code::SYSTEM_ERROR);
$this->response('图片最大为'.$this->cache['image_max'].'k',Code::SYSTEM_ERROR);
}
}else{
if ($file->getSize() > $max) {
$this->response('图片最大为1024K',Code::SYSTEM_ERROR);
$this->response('图片最大为2m',Code::SYSTEM_ERROR);
}
}
}
}else{
if(isset($this->cache['image_max']) && ($this->cache['image_max'] != 0)){
if ($files->getSize() > $this->cache['image_max'] * 1024) {
$this->response('图片最大为'.$this->cache['image_max'],Code::SYSTEM_ERROR);
$this->response('图片最大为'.$this->cache['image_max'].'k',Code::SYSTEM_ERROR);
}
}else{
if ($files->getSize() > $max) {
$this->response('图片最大为1024K',Code::SYSTEM_ERROR);
$this->response('图片最大为2m',Code::SYSTEM_ERROR);
}
}
}
... ...
... ... @@ -81,12 +81,14 @@ class RenewLogic extends BaseLogic
}
DB::beginTransaction();
try {
$this->model->edit(['project_id'=>$this->param['id'],'operator_id'=>$this->manager['id']],['id'=>$this->param['renew_id']]);
if($this->param['renew_id'] != 0){
$this->model->edit(['project_id'=>$this->param['id'],'operator_id'=>$this->manager['id']],['id'=>$this->param['renew_id']]);
}
$param = $this->param;
$param['api_no'] = $info['api_no'] ?? 0;
$this->saveLog($param);
$this->updateProject($this->param['id'],$this->param['type']);
$this->updateProjectBuild($this->param['id'],$this->param['service_duration'],$this->param['plan']);
$this->updateProjectBuild($this->param['id'],$this->param['service_duration'] ?? 0,$this->param['plan']);
DB::commit();
}catch (\Exception $e){
DB::rollBack();
... ... @@ -120,12 +122,12 @@ class RenewLogic extends BaseLogic
public function saveLog($param){
$data = [
'renew_id'=>$param['renew_id'],
'service_duration'=>$param['service_duration'],
'service_duration'=>$param['service_duration'] ?? 0,
'plan'=>$param['plan'],
'old_plan'=>$param['old_plan'],
'type'=>$param['type'],
'old_type'=>$param['old_type'],
'amount'=>$param['amount'],
'amount'=>$param['amount'] ?? 0,
'api_no'=>$param['api_no'],
'project_id'=>$param['id'],
'operator_id'=>$this->manager['id'],
... ...
... ... @@ -68,6 +68,7 @@ class AiBlogLogic extends BaseLogic
}catch (\Exception $e){
$this->fail('保存失败,请联系管理员');
}
$this->sendHttpC([$this->param['route'],'top-blog']);
shell_exec("php artisan save_ai_blog_list {$this->user['project_id']} > /dev/null 2>&1 &");
return $this->success();
}
... ...
... ... @@ -9,7 +9,6 @@
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;
... ...
... ... @@ -81,7 +81,7 @@ class NewsLogic extends BaseLogic
$this->edit(['url' => $route], ['id' => $id]);
$this->curlDelRoute(['new_route'=>$route]);
}
$this->model->saveExtendInfo($id,$this->param['extend'] ?? []);
$this->model->saveExtendInfo($id,$this->param['extend'] ?? [],$this->user['project_id']);
$this->addUpdateNotify(RouteMap::SOURCE_NEWS,$route);
return $this->success(['id'=>$id]);
}
... ... @@ -175,6 +175,9 @@ class NewsLogic extends BaseLogic
RouteMap::delRoute(RouteMap::SOURCE_NEWS, $id, $this->user['project_id']);
$this->delRoute($id);
$this->model->del(['id' => $id]);
//删除扩展字段
$extendInfoModel = new NewsExtendInfo();
$extendInfoModel->del(['news_id'=>$id]);
}
}
DB::commit();
... ...
... ... @@ -5,12 +5,15 @@ namespace App\Http\Logic\Bside\Product;
use App\Exceptions\BsideGlobalException;
use App\Helper\Arr;
use App\Helper\Common;
use App\Helper\Gpt;
use App\Http\Logic\Bside\BaseLogic;
use App\Models\Ai\AiCommand;
use App\Models\Com\NoticeLog;
use App\Models\News\News;
use App\Models\Product\Keyword;
use App\Models\Product\KeywordRelated;
use App\Models\Product\Product;
use App\Models\Project\AggregateKeywordComment;
use App\Models\RouteMap\RouteMap;
use App\Models\User\User;
use Illuminate\Support\Facades\DB;
... ... @@ -345,4 +348,110 @@ class KeywordLogic extends BaseLogic
return $this->success();
}
/**
* @remark :手动添加评论
* @name :saveComment
* @author :lyh
* @method :post
* @time :2025/6/9 14:29
*/
public function saveComment(){
$keywordCommonModel = new AggregateKeywordComment();
if(isset($this->param['id']) && !empty($this->param['id'])){
$id = $this->param['id'];
$keywordCommonModel->edit($this->param,['id'=>$this->param['id']]);
}else{
$param = [
'nickname' => $this->param['nickname'],
'text' => $this->param['text'],
'project_id' => $this->user['project_id'],
'type' => 1,
'uid' => 0,
'start_time'=>$this->param['start_time'],
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
$id = $keywordCommonModel->addReturnId($param);
}
return $this->success(['id'=>$id]);
}
/**
* @remark :保存数据
* @name :sendComment
* @author :lyh
* @method :post
* @time :2025/6/9 11:19
*/
public function sendComment()
{
$aiCommonModel = new AiCommand();
$info = $aiCommonModel->read(['key' => 'tag_comment','project_id'=>$this->user['project_id']]);
if($info === false){
$info = $aiCommonModel->read(['key' => 'tag_comment']);
$info['ai'] = str_replace('50', $this->param['count'], $info['ai']);
}
$text = Gpt::instance()->openai_chat_qqs($info['ai']);
$text = Common::deal_keywords($text);
preg_match_all('/\{[^{}]*\}/', $text, $matches);
$data = [];
$twoMonthsAgo = strtotime('-2 months');
if (!empty($text)) {
foreach ($matches[0] as $item) {
$item = str_replace("'", '"', $item);
// 解码成 PHP 关联数组
$item = json_decode($item, true);
if (!isset($item['name']) || !isset($item['comment'])) {
continue;
}
$randomTimestamp = rand($twoMonthsAgo, time());
$randomDateTime = date('Y-m-d H:i:s', $randomTimestamp);
$data[] = [
'nickname' => $item['name'],
'text' => $item['comment'],
'project_id' => $this->user['project_id'],
'type' => 1,
'uid' => 0,
'start_time'=>$randomDateTime,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
}
}
$keywordCommonModel = new AggregateKeywordComment();
$keywordCommonModel->insertAll($data);
return $this->success($data);
}
/**
* @remark :获取评论
* @name :getComment
* @author :lyh
* @method :post
* @time :2025/6/9 11:46
*/
public function getComment($map,$page,$row){
$keywordCommonModel = new AggregateKeywordComment();
$map['project_id'] = $this->user['project_id'];
$lists = $keywordCommonModel->lists($map,$page,$row);
return $this->success($lists);
}
/**
* @remark :删除评论
* @name :delComment
* @author :lyh
* @method :post
* @time :2025/6/9 11:48
*/
public function delComment(){
$keywordCommonModel = new AggregateKeywordComment();
if($this->param['id'] == 0){
$keywordCommonModel->del(['project_id'=>$this->user['project_id']]);
}else{
$keywordCommonModel->del(['id'=>$this->param['id']]);
}
return $this->success();
}
}
... ...
... ... @@ -101,6 +101,7 @@ class RankDataLogic extends BaseLogic
'keyword_num' => $project['deploy_build']['keyword_num'],
'compliance_day' => $project['finish_remain_day'] ?? 0,
'remain_day' => $project['remain_day'],
'seo_remain_day' => $project['seo_remain_day'],
'g_top_plan' => $g_top_plan ?? [],
];
//小语种列表
... ... @@ -553,7 +554,7 @@ class RankDataLogic extends BaseLogic
$without_extension_project_ids = [658]; //是否达标只统计主词的
$extension_project_ids = [354]; //扩展词也到达标的
$compliance_project_ids = [2163,257,823,1750,497]; //直接达标处理的
$ceaseProjectId = [354, 378, 649, 1226, 1283, 1703, 1893, 2066, 2250,2193];//暂停的项目
$ceaseProjectId = [354, 378, 649, 1226, 1283, 1703, 1893, 2066, 2250,2193,2399,1685];//暂停的项目
$uptimeProjectId = [1434,1812,276,2414,2974];//按上线时间统计的项目
//一个项目多个api_no
$multiple_api_no_project_ids = [
... ...
... ... @@ -74,6 +74,7 @@ class TranslateLogic extends BaseLogic
}
$arr2 = [];
foreach ($text_array as $val) {
$val = str_replace(['%22', '%', '+'], ['"', '%', '+'], $val);
$val = rawurldecode($val);
$val = str_replace(' ','',$val);//处理特殊字符
$val = trim(str_replace(' ','',$val));
... ... @@ -272,14 +273,10 @@ class TranslateLogic extends BaseLogic
$data = [];
if(!empty($this->param['data'])){
//处理传递的data
foreach ($this->param['data'] as $k => $v){
if(!empty($v) && is_array($v)){
foreach ($v as $text => $translate){
$text = str_replace(['%22', '%', '+'], ['"', '%', '+'], $text);
$translate = str_replace(['%22', '%', '+'], ['"', '%', '+'], $translate);
$data[$text] = $translate;
}
}
foreach ($this->param['data'] as $k => $translate){
$text = str_replace(['%22', '%', '+'], ['"', '%', '+'], $translate[0]);
$translate = str_replace(['%22', '%', '+'], ['"', '%', '+'], $translate[1]);
$data[$text] = $translate;
}
$this->param['data'] = $data;
}
... ...
... ... @@ -7,6 +7,7 @@ use App\Exceptions\AsideGlobalException;
use App\Exceptions\BsideGlobalException;
use App\Helper\Common;
use App\Models\Domain\DomainInfo;
use App\Models\Manage\ManageHr;
use App\Models\Project\Project;
use App\Models\Scoring\ScoringSystem;
use App\Models\Sms\SmsLog;
... ... @@ -292,6 +293,9 @@ class UserLoginLogic
$info['service_duration'] = $project['deploy_build']['service_duration'] ?? 0;
$info['is_comment'] = $project['deploy_build']['is_comment'] ?? 0;
$info['is_ai_blog_send'] = $project['deploy_optimize']['is_ai_blog_send'] ?? 0;
$info['tech_leader'] = $project['deploy_optimize']['tech_leader'] ?? 0;
$manageModel = new ManageHr();
$info['tech_leader_name'] = $manageModel->getName($project['deploy_optimize']['tech_leader'] ?? 0);
$info['remain_day'] = $project['remain_day'] ?? 0;
$info['project_created_at'] = $project['created_at'];
$info['type'] = $project['type'] ?? 1;
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderListRequest 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 [
'page' => 'nullable|integer',
'size' => 'nullable|integer',
'project_id' => 'nullable|integer', // 产品ID
'status' => 'nullable|in:0,1,2,3|integer',
'search' => 'nullable|string', // 搜索关键词
];
}
}
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderUpdateRequest 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
* A端更新售后工单
*/
public function rules()
{
return [
'status' => 'nullable|in:0,1,2,3|integer',
'engineer_ids' => 'nullable|array',
'content' => 'nullable|string',
'files' => 'nullable|array',
];
}
}
... ...
<?php
namespace App\Http\Requests\Bside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderCreateRequest 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 [
'product' => 'required|string',
'content' => 'required|string',
'files' => 'nullable|array',
];
}
}
... ...
<?php
namespace App\Http\Requests\Bside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderUpdateRequest 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',
'engineer_ids' => 'nullable|array',
'content' => 'nullable|string',
'files' => 'nullable|array',
];
}
}
... ...
... ... @@ -13,6 +13,8 @@ class NoticeLog extends Base
const TYPE_PROJECT = 'project';
const TYPE_RANK_DATA = 'rank_data';
const TYPE_INIT_PROJECT = 'init_project';
const TYPE_INIT_KEYWORD_COMMON = 'init_keyword_common';//聚合页关键词评论
const TYPE_GENERATE_COUNT_CHARTS = 'generate_count_charts';//聚合页关键字图表生成
const TYPE_COPY_PROJECT = 'copy_project';
const TYPE_INIT_KEYWORD = 'init_keyword';
const DELETE_PRODUCT_CATEGORY = 'delete_product_category';
... ...
... ... @@ -80,7 +80,7 @@ class News extends Base
$arr = json_decode($info['values']);
foreach ($arr as $k1=>$v1){
$v1 = (array)$v1;
$v1['url'] = getImageUrl($v1['url'],$this->user['storage_type'],$this->user['project_location']);
$v1['url'] = getImageUrl($v1['url']);
$arr[$k1] = $v1;
}
$v['values'] = $arr;
... ... @@ -89,9 +89,9 @@ class News extends Base
foreach ($arr1 as $k1=>$v1){
$v1 = (array)$v1;
if(isset($v1['url'])){
$v1['url'] = getFileUrl($v1['url'],$this->user['storage_type'],$this->user['project_location'],$this->user['file_cdn'] ?? 0);
$v1['url'] = getFileUrl($v1['url']);
}else{
$v1 = getFileUrl($v1,$this->user['storage_type'],$this->user['project_location'],$this->user['file_cdn'] ?? 0);
$v1 = getFileUrl($v1);
}
$arr1[$k1] = $v1;
}
... ... @@ -109,7 +109,7 @@ class News extends Base
* @method :post
* @time :2023/11/9 15:02
*/
public function saveExtendInfo($news_id,$extend){
public function saveExtendInfo($news_id,$extend,$project_id){
//先删除以前的数据
$extendInfoModel = new NewsExtendInfo();
$extendInfoModel->del(['news_id'=>$news_id]);
... ... @@ -120,7 +120,7 @@ class News extends Base
if(empty($v['values'])){
continue;
}
$v = $this->saveHandleExtend($v,$news_id);
$v = $this->saveHandleExtend($v,$news_id,$project_id);
$extendInfoModel->add($v);
}
return true;
... ... @@ -133,7 +133,7 @@ class News extends Base
* @method :post
* @time :2023/12/6 15:11
*/
public function saveHandleExtend(&$v,$news_id){
public function saveHandleExtend(&$v,$news_id,$project_id){
unset($v['title']);
if($v['type'] == 3){
foreach ($v['values'] as $k1=>$v1){
... ... @@ -148,7 +148,7 @@ class News extends Base
}
$v['values'] = json_encode($v['values']);
}
$v['project_id'] = $this->user['project_id'];
$v['project_id'] = $project_id;
$v['news_id'] = $news_id;
return $v;
}
... ...
<?php
/**
* @remark :
* @name :AggregateKeywordComment.php
* @author :lyh
* @method :post
* @time :2025/6/3 17:42
*/
namespace App\Models\Project;
use App\Models\Base;
class AggregateKeywordComment extends Base
{
protected $table = 'gl_aggregate_keyword_comment';
}
... ...
<?php
namespace App\Models\Project;
use App\Helper\Arr;
use App\Models\Base;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
class ProjectKeywordAiTask extends Base
{
//设置关联表名
protected $table = 'gl_project_keyword_ai_task';
const STATUS_PENDING = 0;
const STATUS_SUCCESS = 1;
const STATUS_FAIL = 2;
public static function add_task($project_id){
$task = self::where('project_id', $project_id)->where('status', self::STATUS_PENDING)->first();
if($task){
throw new \Exception('该项目有未执行的任务,请勿重复添加');
}
$model = new self();
$model->project_id = $project_id;
$model->save();
Redis::lpush('projectKeywordAiTask', $project_id);
}
public static function getPendingTask(){
//有其他任务 就取其他任务 没有其他任务运行未结束的任务
$project_id = Redis::rpop('projectKeywordAiTask');
$data = [];
if($project_id){
$data = self::where('status', self::STATUS_PENDING)->where('project_id', $project_id)->orderBy('id', 'asc')->first();
}
if($data){
return $data;
}
return self::where('status', self::STATUS_PENDING)->orderBy('id', 'asc')->first();
}
/**
* 重试任务
* @param $id
* @param $remark
* @author zbj
* @date 2023/11/9
*/
public static function retry($id, $remark)
{
DB::beginTransaction();
try {
//行锁 避免脏读写
$data = self::where('id', $id)->lockForUpdate()->first();
$data->retry = $data->retry + 1;
if ($data->retry > 3) {
$data->status = self::STATUS_FAIL;
}else{
$data->status = self::STATUS_PENDING;
}
$data->remark = mb_substr($remark, 0, 250);
$data->save();
DB::commit();
} catch (\Exception $e) {
DB::rollback();
Log::error('project_keyword_ai_task retry error:' . $e->getMessage());
}
}
/**
* 完成
* @param $id
* @param $update_rows
* @author zbj
* @date 2023/11/9
*/
public static function finish($id, $update_rows){
DB::beginTransaction();
try {
//行锁 避免脏读写
$data = self::where('id', $id)->lockForUpdate()->first();
$data->status = self::STATUS_SUCCESS;
$data->update_rows = $update_rows;
$data->save();
DB::commit();
} catch (\Exception $e) {
DB::rollback();
Log::error('project_keyword_ai_task finish error:' . $e->getMessage());
}
}
}
... ...
<?php
/**
* @remark :
* @name :TranslateBigProject.php
* @author :lyh
* @method :post
* @time :2025/6/4 14:40
*/
namespace App\Models\WebSetting;
use App\Models\Base;
/**
* @remark :需要翻译的大网站
* @name :TranslateBigProject
* @author :lyh
* @method :post
* @time :2025/6/4 14:41
*/
class TranslateBigProject extends Base
{
protected $table = 'gl_translate_big_project';
}
... ...
<?php
namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
use App\Models\Project\Project;
use App\Models\User\User;
class WorkOrder extends Base
{
protected $table = 'gl_work_orders';
const STATUS_PEDDING = 0; // 待处理
const STATUS_PROCESSING = 1; // 处理中
const STATUS_COMPLETED = 2; // 已完成
const STATUS_CLOSED = 3; // 已关闭
/**
* 工单操作日志
*/
public function logs()
{
return $this->hasMany(WorkOrderLog::class, 'work_order_id', 'id');
}
/**
* 提交工单的用户
*/
public function user()
{
return $this->belongsTo(User::class, 'project_user_id', 'id');
}
/**
* 工单分配的管理员
*/
public function manager()
{
return $this->belongsTo(Manage::class, 'manage_id', 'id');
}
/**
* 工单分配的工程师
*/
public function engineer()
{
return $this->belongsTo(Manage::class, 'engineer_id', 'id');
}
/** 工单所属项目 */
public function project()
{
return $this->belongsTo(Project::class, 'project_id', 'id');
}
}
... ...
<?php
namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
class WorkOrderLog extends Base
{
protected $table = 'gl_work_order_logs';
const STATUS_PEDDING = 0; // 待处理
const STATUS_PROCESSING = 1; // 处理中
const STATUS_COMPLETED = 2; // 已完成
const STATUS_CLOSED = 3; // 已关闭
/**
* 所属工单
*/
public function workOrder()
{
return $this->belongsTo(WorkOrder::class, 'work_order_id');
}
/**
* 分配的工单负责人,工程师等
*/
public function manager()
{
return $this->belongsTo(Manage::class, 'manage_id', 'id');
}
}
... ...
<?php
namespace App\Services;
use App\DingDepartment;
use App\DingUser;
/**
* 文档 https://apihub.cmer.com/docs/cmerdingtalk.html
*/
class DingTalkService
{
protected $appKey;
protected $appSecret;
protected $robotCode;
protected $headers = [];
protected $bashUrl = 'https://api.cmer.com';
public function __construct()
{
$this->appKey = env('DING_TALK_APP_KEY');
$this->appSecret = env('DING_TALK_APP_SECRET');
$this->robotCode = env('DING_TALK_APP_KEY');
$this->headers = [
"Content-Type" => "application/json",
"apikey" => "UkzZljFv83Z2qBi5YR1o3f2otAVWtug6",
"X-CmerApi-Host" => "cmerdingtalk.p.cmer.com",
];
}
/** 发起请求 */
public function send_request(string $method, string $url, array $payload = [], array $params = [])
{
$client = new \GuzzleHttp\Client();
$options = [
'headers' => $this->headers,
'json' => $payload
];
if (!empty($params))
$options['query'] = $params;
$response = $client->request($method, $url, $options);
return $response;
}
/** 批量发送私聊消息 */
public function danliao(string $text, array $user_ids)
{
$endpoint = '/v1/danliao';
$payload = [
"appKey" => $this->appKey,
"appSecret" => $this->appSecret,
"robotCode" => $this->robotCode,
"msg_param" => $text,
"user_ids" => $user_ids
];
return $this->send_request('POST', $this->bashUrl . $endpoint, $payload);
}
/** 批量发送Ding */
public function danliao_ding(string $text, array $user_ids)
{
$endpoint = '/v1/danliao_ding';
$payload = [
"appKey" => $this->appKey,
"appSecret" => $this->appSecret,
"robotCode" => $this->robotCode,
"content" => $text,
"receiver_user_id_list" => $user_ids
];
return $this->send_request('POST', $this->bashUrl . $endpoint, $payload);
}
}
... ...
... ... @@ -25,7 +25,7 @@ class ProjectServer
<section data-section="section" data-screen="screen-large" class="section-404-wrap-block section-block-error404"
id="sectionIdyxqu938">
<div class="layout" data-unable="demo01-error404">
<img src="https://ecdn6.globalso.com/upload/m/image_other/2023-10/6528a87e594db30162.png" />
<img src="https://ecdn6.globalso.com/upload/m/image_other/2023-10/6528a87e594db30162.png" alt=""/>
</div>
<p style="text-align: center">SORRY. THE PAGE HAS EITHER MOVED OR CANNOT BE FOUND.</p>
<style>
... ... @@ -38,7 +38,7 @@ class ProjectServer
.section-block-error404 img {
width: 400px;
}
@media only screen and (max-width:500) {
@media only screen and (max-width:500px) {
.section-block-error404 img {
max-width: 100%;
}
... ...
... ... @@ -12,6 +12,7 @@
"beyondcode/laravel-websockets": "^1.14",
"doctrine/dbal": "^3.6",
"fruitcake/laravel-cors": "^2.0",
"g4t/swagger": "^4.0",
"guzzlehttp/guzzle": "^7.0.1",
"hashids/hashids": "^4.1",
"intervention/image": "^2.7",
... ...
<?php
return [
/*
|--------------------------------------------------------------------------
| API Title
|--------------------------------------------------------------------------
|
| The title of your API documentation.
|
*/
"title" => env("SWAGGER_TITLE", "Globalso V6 Api Documentation"),
/*
|--------------------------------------------------------------------------
| API Description
|--------------------------------------------------------------------------
|
| The description of your API.
|
*/
"description" => env("SWAGGER_DESCRIPTION", "Laravel autogenerate swagger"),
/*
|--------------------------------------------------------------------------
| API Email
|--------------------------------------------------------------------------
|
| The email associated with your API documentation.
|
*/
"email" => env("SWAGGER_EMAIL", "bill@ai.cc"),
/*
|--------------------------------------------------------------------------
| API Version
|--------------------------------------------------------------------------
|
| The version of your API.
|
*/
"version" => env("SWAGGER_VERSION", "3.0.7"),
/*
|--------------------------------------------------------------------------
| Documentation Auth
|--------------------------------------------------------------------------
|
| This options to enable documentation auth
|
*/
"enable_auth" => false,
"username" => "admin",
"password" => "pass",
"sesson_ttl" => 100000,
/*
|--------------------------------------------------------------------------
| Enable Response Schema
|--------------------------------------------------------------------------
|
| Whether to enable response schema or not.
|
*/
"enable_response_schema" => true,
"suggestions_select_input" => false,
"load_from_json" => false,
/*
|--------------------------------------------------------------------------
| Authentication Middlewares
|--------------------------------------------------------------------------
|
| List of middleware names used for authentication.
|
*/
"auth_middlewares" => [
"auth",
"auth:api",
"aloginauth",
"bloginauth",
],
/*
|--------------------------------------------------------------------------
| API URL
|--------------------------------------------------------------------------
|
| The URL path for accessing your API documentation.
|
*/
"url" => env("SWAGGER_URL", "swagger/documentation"),
/*
|--------------------------------------------------------------------------
| Issues URL
|--------------------------------------------------------------------------
|
| The URL path for accessing issues related to your API documentation.
|
*/
"issues_url" => env("SWAGGER_ISSUE_URL", "swagger/issues"),
/*
|--------------------------------------------------------------------------
| Enable Swagger
|--------------------------------------------------------------------------
|
| Whether Swagger is enabled or not.
|
*/
"enable" => env('SWAGGER_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Show Prefix
|--------------------------------------------------------------------------
|
| List of prefixes to show in Swagger.
|
*/
"show_prefix" => [],
/*
|--------------------------------------------------------------------------
| Include Web Routes
|--------------------------------------------------------------------------
|
| If you want to includes web.php routes, then enable this
|
*/
"include_web_routes" => env('SWAGGER_INCLUDE_WEB_ROUTES', true),
/*
|--------------------------------------------------------------------------
| API Versions
|--------------------------------------------------------------------------
|
| List of versions to show in Swagger.
|
*/
"versions" => [
"all",
// "v1"
],
"default" => "all",
/*
|--------------------------------------------------------------------------
| Servers
|--------------------------------------------------------------------------
|
| List of servers associated with your API.
|
*/
"servers" => [
[
"url" => env("APP_URL"),
"description" => "localhost"
]
],
/*
|--------------------------------------------------------------------------
| Security Schemes
|--------------------------------------------------------------------------
|
| Security schemes used in your API.
|
*/
"security_schemes" => [
"APIKeyHeader" => [
"type" => "apiKey",
"name" => "token",
"in" => "header"
],
],
/*
|--------------------------------------------------------------------------
| Spatie Query Builder
|--------------------------------------------------------------------------
|
| Enable it if you using Spatie query builder package to add spatie filters in all GET routes.
|
*/
"spatie_query_builder" => false,
/*
|--------------------------------------------------------------------------
| Status
|--------------------------------------------------------------------------
|
| HTTP response statuses for various methods.
|
*/
"status" => [
"GET" => [
"200" => [
"description" => "Successful Operation",
],
"404" => [
"description" => "Not Found"
]
],
"POST" => [
"200" => [
"description" => "Successful Operation",
],
"422" => [
"description" => "Validation Issues"
]
],
"PUT" => [
"200" => [
"description" => "Successful Operation",
],
"404" => [
"description" => "Not Found"
],
"405" => [
"description" => "Validation exception"
]
],
"PATCH" => [
"200" => [
"description" => "Successful Operation",
],
"404" => [
"description" => "Not Found"
],
"405" => [
"description" => "Validation exception"
]
],
"DELETE" => [
"200" => [
"description" => "successful Operation",
],
"404" => [
"description" => "page Not Found"
]
],
],
];
... ...
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFailedJobsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('failed_jobs');
}
}
... ... @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePersonalAccessTokensTable extends Migration
class CreateWorkOrdersTable extends Migration
{
/**
* Run the migrations.
... ... @@ -13,15 +13,21 @@ class CreatePersonalAccessTokensTable extends Migration
*/
public function up()
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
Schema::create('gl_work_orders', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamps();
$table->string('product')->nullable()->comment('相关产品,工单类型');
$table->longText('content')->comment('工单图文描述');
$table->json('files')->nullable()->comment('附件');
$table->integer('status')->index()->default(0)->comment('工单状态,0:待处理, 1:处理中,2:已完成, 3:已关闭');
$table->integer('project_user_id')->index()->comment('客户ID');
$table->integer('project_id')->index()->comment('项目ID');
$table->integer('manage_id')->index()->comment('A端项目负责人ID, gl_manage 表ID');
$table->integer('engineer_id')->index()->comment('处理技术,工程师id, gl_manage 表ID');
$table->timestamp('end_at')->nullable()->comment('完成时间');
});
# 添加表注释
\Illuminate\Support\Facades\DB::statement("ALTER TABLE gl_work_orders comment '工单表'");
}
/**
... ... @@ -31,6 +37,6 @@ class CreatePersonalAccessTokensTable extends Migration
*/
public function down()
{
Schema::dropIfExists('personal_access_tokens');
Schema::dropIfExists('gl_work_orders');
}
}
... ...
... ... @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
class CreateWorkOrderLogsTable extends Migration
{
/**
* Run the migrations.
... ... @@ -13,15 +13,19 @@ class CreateUsersTable extends Migration
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
Schema::create('gl_work_order_logs', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
$table->integer('work_order_id')->index();
$table->integer('manage_id')->comment('gl_manage 表ID,操作人ID');
$table->boolean('ding')->default(false)->comment('是否钉钉通知');
$table->longText('content')->nullable()->comment('处理结果,图文描述');
$table->json('files')->nullable()->comment('附件');
$table->integer('status')->index()->default(0)->comment('工单状态,0:待处理, 1:处理中, 2:已完成');
$table->timestamp('end_at')->nullable()->comment('完成时间');
$table->unique(['work_order_id', 'manage_id'], 'work_order_manage_unique'); # 唯一索引,防止同一工单被同一人多次操作,同一工单,可以多人协作
});
\Illuminate\Support\Facades\DB::statement('ALTER TABLE gl_work_order_logs comment "工单操作日志表,分配工程师时创建,工程师完成时修改状态"');
}
/**
... ... @@ -31,6 +35,6 @@ class CreateUsersTable extends Migration
*/
public function down()
{
Schema::dropIfExists('users');
Schema::dropIfExists('gl_work_order_logs');
}
}
... ...
... ... @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
class WorkOrderLogsAddForeignId extends Migration
{
/**
* Run the migrations.
... ... @@ -13,11 +13,12 @@ class CreatePasswordResetsTable extends Migration
*/
public function up()
{
Schema::create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::table('gl_work_order_logs', function (Blueprint $table) {
// 字段已存在,给字段添加外键约束
$table->foreign('work_order_id')
->references('id')->on('gl_work_orders')
->onDelete('cascade'); // 设置级联删除
});
}
/**
... ... @@ -27,6 +28,9 @@ class CreatePasswordResetsTable extends Migration
*/
public function down()
{
Schema::dropIfExists('password_resets');
Schema::table('gl_work_order_logs', function (Blueprint $table) {
// 删除外键约束
$table->dropForeign(['work_order_id']);
});
}
}
... ...
不能预览此文件类型
... ... @@ -149,6 +149,7 @@ Route::middleware(['aloginauth'])->group(function () {
//人事管理
Route::prefix('hr')->group(function () {
Route::any('/', [Aside\Manage\HrController::class, 'list'])->name('admin.hr');
Route::any('/getManagerList', [Aside\Manage\HrController::class, 'getManagerList'])->name('admin.hr_getManagerList');
Route::any('/info', [Aside\Manage\HrController::class, 'info'])->name('admin.hr_info');
Route::post('/save', [Aside\Manage\HrController::class, 'save'])->name('admin.hr_save');
Route::post('/sort', [Aside\Manage\HrController::class, 'sort'])->name('admin.hr_sort');
... ... @@ -208,6 +209,7 @@ Route::middleware(['aloginauth'])->group(function () {
Route::any('/getKeywordPrefix', [Aside\Project\KeywordPrefixController::class, 'getKeywordPrefix'])->name('admin.keyword_getKeywordPrefix');
Route::any('/save', [Aside\Project\KeywordPrefixController::class, 'save'])->name('admin.keyword_save');
Route::any('/del', [Aside\Project\KeywordPrefixController::class, 'del'])->name('admin.keyword_del');
Route::any('/generateCountCharts', [Aside\Project\ProjectController::class, 'generateCountCharts'])->name('admin.keyword_generateCountCharts');
});
//企业资料库
Route::prefix('enterprise_product')->group(function () {
... ... @@ -246,6 +248,13 @@ Route::middleware(['aloginauth'])->group(function () {
Route::post('/save_follow', [Aside\Task\TaskController::class, 'save_follow'])->name('admin.task_save_follow');
Route::any('/getUserTaskList', [Aside\Task\TaskController::class, 'getUserTaskList'])->name('admin.task_getUserTaskList');
});
// 售后工单
Route::prefix('workorder')->group(function () {
Route::get('/', [Aside\WorkOrder\WorkOrderController::class, 'index'])->name('admin.workorder.index')->summary('A端工程师的工单列表');
Route::get('/manager', [Aside\WorkOrder\WorkOrderController::class, 'manager'])->name('admin.workorder.manager')->summary('A端管理员的工单列表');
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('devops')->group(function () {
Route::any('/', [Aside\Devops\ServerConfigController::class, 'lists'])->name('admin.devops.lists');
... ...
... ... @@ -269,6 +269,16 @@ Route::middleware(['bloginauth'])->group(function () {
Route::any('/save',[\App\Http\Controllers\Bside\Setting\WebSettingAmpController::class, 'save'])->name('amp_save');
});
});
// 售后工单
Route::prefix('workorder')->group(function () {
Route::get('/', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'index'])->name('workorder.index')->summary('B端售后工单列表');
Route::post('/', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'store'])->name('workorder.store')->summary('B端创建工单');
Route::get('/{id}', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'show'])->name('workorder.show')->summary('B端查看工单');
Route::post('/{id}', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'update'])->name('workorder.update')->summary('B端完结工单');
Route::delete('/{id}', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'destroy'])->name('workorder.destroy')->summary('B端删除工单,已完结的工单无法删除');
});
//产品
Route::prefix('product')->group(function () {
//产品
... ... @@ -317,6 +327,10 @@ Route::middleware(['bloginauth'])->group(function () {
Route::any('keyword/batchKeywordIsVideo', [\App\Http\Controllers\Bside\Product\KeywordController::class, 'batchKeywordIsVideo'])->name('product_keyword_batchKeywordIsVideo');
Route::any('keyword/batchKeywordFiled', [\App\Http\Controllers\Bside\Product\KeywordController::class, 'batchKeywordFiled'])->name('product_keyword_batchKeywordFiled');
Route::any('keyword/delRelatedProductId', [\App\Http\Controllers\Bside\Product\KeywordController::class, 'delRelatedProductId'])->name('product_keyword_delRelatedProductId');
Route::any('keyword/sendComment', [\App\Http\Controllers\Bside\Product\KeywordController::class, 'sendComment'])->name('product_keyword_sendComment');
Route::any('keyword/getComment', [\App\Http\Controllers\Bside\Product\KeywordController::class, 'getComment'])->name('product_keyword_getComment');
Route::any('keyword/delComment', [\App\Http\Controllers\Bside\Product\KeywordController::class, 'delComment'])->name('product_keyword_delComment');
Route::any('keyword/saveComment', [\App\Http\Controllers\Bside\Product\KeywordController::class, 'saveComment'])->name('product_keyword_saveComment');
//产品参数
Route::get('attr', [\App\Http\Controllers\Bside\Product\AttrController::class, 'index'])->name('product_attr');
Route::get('attr/info', [\App\Http\Controllers\Bside\Product\AttrController::class, 'info'])->name('product_attr_info');
... ...