moshaorui hai 3 días
pai
achega
389f6f7951

+ 1 - 0
app/Console/Commands/SyncBrFix.php

@@ -32,6 +32,7 @@ class SyncBrFix extends Command
 
     public function handle()
     {
+        exit;
 
         $sqlFile = fopen('update_products.sql', 'a') or die("无法创建SQL文件");
 

+ 113 - 0
app/Console/Commands/TimerSsmPost.php

@@ -0,0 +1,113 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Distributor\Repositories\SmmPost;
+use App\Distributor\Repositories\SmmPostLog;
+use App\Distributor\Repositories\SmmUserAccount;
+use App\Libraries\CommonHelper;
+use App\Services\SmmService;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Symfony\Component\DomCrawler\Crawler;
+
+
+/**
+ * 定时任务:发送社媒帖子
+ * php artisan timer:ssmPost
+ */
+class TimerSsmPost extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'timer:ssmPost';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '发送社媒帖子';
+
+
+    public function handle()
+    {
+        // 发送社媒帖子
+        $waitPost = SmmPost::getWaitPost();
+        foreach ($waitPost as $post) {
+            //插入post_logs表
+            $accountIds = explode(',', $post->account_ids);
+            $accounts = SmmUserAccount::getAccountsByIds($accountIds);
+            foreach ($accounts as $account) {
+                //生成post_logs表数据
+                $data = [
+                    'post_id' => $post->id,
+                    'account_id' => $account->id,
+                    'account_name' => $account->name,
+                    'status' => 0,
+                    'remark' => '',
+                    'created_at' => Carbon::now(),
+                    'updated_at' => Carbon::now(),
+                    'dist_id' => $account->dist_id,
+                    'media_name' => $account->getParent->name,
+                    'response_ids'=> '',
+                    'send_time' => $post->send_time,
+                ];
+                SmmPostLog::createLog($data);
+            }
+            $post->status = 1;
+            $post->save();
+        }
+        //dd('middle');
+        //发送社媒帖子开始
+        $sendLog = SmmPostLog::getSendLog();
+        foreach ($sendLog as $log) {
+            //获取帖子内容
+            $post = SmmPost::getPostById($log->post_id);
+            $message = $post->message;
+            $imageVideoUrl = $post->image_video_url;
+           // $imageVideoUrl = storage_path($imageVideoUrl);
+            $mediaName = $log->media_name;
+            $postType = $post->post_type;
+            $accountInfo = SmmUserAccount::getAccountById($log->account_id);
+            $accessToken = $accountInfo->access_token;
+            //发送帖子
+            $configData = ['accountInfo' => $accountInfo->toArray()];
+            $ssmService = new SmmService($mediaName,$configData);
+            if ($postType == 0) {
+                //图片帖子
+                $imageVideoUrl = explode(',', $imageVideoUrl);
+                foreach ($imageVideoUrl as $key => $url) {
+                    $imageVideoUrl[$key] = CommonHelper::ossUrl($url);
+                }
+                $response = $ssmService->postImage($message, $imageVideoUrl,$accessToken);
+            } else {
+                $imageVideoUrl = CommonHelper::ossUrl($imageVideoUrl);
+                $response = $ssmService->postVideo($message, $imageVideoUrl,$accessToken);
+            }
+           // dd($response);
+            //更新post_logs表
+            if ($response['status'] == true) {
+                $log->status = 1;
+                $log->response_ids = json_encode($response['data']['responseIds']);
+                $log->request_content = json_encode($response['data']['requestContent']);
+                $log->response_content = json_encode($response['data']['responseContent']);
+                $log->updated_at = Carbon::now();
+                $log->send_time = Carbon::now();
+                $log->save();
+            } else {
+                $log->status = 2;
+                $log->remark = $response['data'];
+                $log->updated_at = Carbon::now();
+                $log->send_time = Carbon::now();
+                $log->save();
+            }
+        }
+        dd('发送社媒帖子完成');
+    }
+
+}

+ 22 - 5
app/Distributor/Actions/SmmAddAccount.php

@@ -11,7 +11,9 @@ use Illuminate\Http\Request;
 
 class SmmAddAccount extends RowAction
 {
-
+    protected $title = null;
+    //媒体名称
+    protected $mediaName = null;
     /**
      * 返回字段标题
      *
@@ -19,9 +21,18 @@ class SmmAddAccount extends RowAction
      */
     public function title()
     {
+        if ($this->title) {
+            return $this->title;
+        }
         return admin_trans_label('add_platform_account');
     }
 
+    public function setData($title,$mediaName)
+    {
+        $this->title = $title;
+        $this->mediaName = $mediaName;
+    }
+
     /**
      * 处理请求
      *
@@ -54,10 +65,16 @@ class SmmAddAccount extends RowAction
 
     public function parameters()
     {
-        return [
-            // 发送当前行 username 字段数据到接口
-            'name' => $this->row->name,
-        ];
+        if ($this->mediaName) {
+            return [
+                'name' => $this->mediaName,
+            ];
+        } else {
+            return [
+                // 发送当前行 username 字段数据到接口
+                'name' => $this->row->name,
+            ];
+        }
     }
 
 }

+ 103 - 28
app/Distributor/Controllers/SmmPostController.php

@@ -38,47 +38,38 @@ class SmmPostController extends AdminDistController
             $form->action('ssm-post');
             $form->disableListButton();
             $form->multipleSteps()
-                ->remember(false)
+                ->remember()
                 ->width('950px')
                 ->add('选择本地媒体', function ($step) {
                     $step->radio('send_type',admin_trans_label('send_type'))
                         ->when([1], function ($step) {
-                            $step->datetime('send_time', admin_trans_label('send_time'))->required();
+                            $step->datetime('send_time', '<span style="color:#bd4147;">*</span> '.admin_trans_label('send_time'))->placeholder(' ');
                         })
-                        ->options([ 0=>admin_trans_label('immediate'), 1=>admin_trans_label('timing')])->required()->default(0);
-                            $step->text('message', admin_trans_label('post_title'))->required()->maxLength(20);
-                            $step->radio('media_type',admin_trans_label('media_type'))
+                        ->options([ 0=>admin_trans_label('immediate'), 1=>admin_trans_label('timing')])->default(0);
+                    $step->radio('post_type',admin_trans_label('media_type'))
                         ->when([0], function ($step) {
-                            $step->multipleImage('image_url', admin_trans_label('images'))
-                                ->disk('local')
+                            $step->textarea('image_message', '<span style="color:#bd4147;">*</span> '.admin_trans_label('post_message'))->rows(3)->placeholder(' ');
+                            $step->multipleImage('image_url', '<span style="color:#bd4147;">*</span> '.admin_trans_label('images'))
                                 ->retainable()//禁止删OSS图
                                 ->sortable() // 可拖动排序
                                 ->removable() // 可移除图片
                                 ->autoUpload() // 自动上传
                                 ->uniqueName()
-                                ->limit(config('admin.upload.oss_image.limit'))
+                                ->limit(5)
                                 ->accept(config('admin.upload.oss_image.accept'))
-                                ->maxSize(config('admin.upload.oss_image.max_size'))
-                                ->required()
-                                ->saving(function ($reslut) {
-                                    return json_encode($reslut);
-                                });
+                                ->maxSize(config('admin.upload.oss_image.max_size'));
                         })
                         ->when([1], function ($step)  {
-                            $step->file('video_url')
-                                ->disk('local') // 使用本地存储
+                            $step->textarea('video_message', '<span style="color:#bd4147;">*</span> '.admin_trans_label('post_message'))->rows(3)->placeholder(' ');
+                            $step->file('video_url','<span style="color:#bd4147;">*</span> '.admin_trans_label('video'))
                                 ->uniqueName()
                                 ->autoUpload()
-                                ->required()
                                 ->accept(config('admin.upload.oss_video.accept'))
                                 ->maxSize(config('admin.upload.oss_video.max_size'))
-                                ->chunkSize(1024)
-                                ->required()
-                                ->saving(function ($reslut) {
-                                    return json_encode($reslut);
-                                });
+                                ->chunkSize(1024);
                         })
-                        ->options([ 0=>admin_trans_label('images'), 1=>admin_trans_label('videos')])->required()->default(0);
+                        ->options([ 0=>admin_trans_label('images'), 1=>admin_trans_label('videos')])->default(0);
+                    $this->stepLeaving($step,0);
                 })
                 ->add('选择传单社媒平台', function ($step) {
                     $rootAccounts = SmmUserAccount::getUserAccounts();
@@ -86,7 +77,8 @@ class SmmPostController extends AdminDistController
                     foreach ($rootAccounts as $account) {
                         $listBoxOptions[$account->id] = $account->name . ' ('.$account->getParent->name.')';
                     }
-                    $step->listbox('account_ids', admin_trans_label('accountsSelect'))->required()->options($listBoxOptions);
+                    $step->listbox('account_ids', '<span style="color:#bd4147;">*</span> '.admin_trans_label('accountsSelect'))->options($listBoxOptions);
+                    $this->stepLeaving($step,1);
                 });
         });
     }
@@ -96,16 +88,18 @@ class SmmPostController extends AdminDistController
      */
     public function store() {
         $post = $_POST;
-        if (isset($post['upload_column']) && $post['upload_column'] == 'image_url') {
+        if (isset($post['upload_column']) && ($post['upload_column'] == 'image_url' || $post['upload_column'] == 'video_url')) {
             // 上传图片或视频
             return $this->upload();
         }
         if (isset($post['ALL_STEPS']) && $post['ALL_STEPS'] == '1') {
-            $media_type = $post['media_type'];
-            if ($media_type == 0) {
+            $post_type = $post['post_type'];
+            if ($post_type == 0) {
                 $image_video_url = $post['image_url'];
+                $post['message'] = $post['image_message'];
             } else {
                 $image_video_url = $post['video_url'];
+                $post['message'] = $post['video_message'];
             }
             if ($this->checkStoragePath($image_video_url) === false) {
                 return '发送失败,请检查上传文件是否存在';
@@ -116,6 +110,7 @@ class SmmPostController extends AdminDistController
                 //转换时间格式
                 $send_time = Carbon::createFromFormat('Y-m-d H:i:s', $post['send_time']);
             }
+            //dd($post);
             //保存数据
             SmmPost::create($post,$send_time,$image_video_url);
             //最后一步
@@ -132,7 +127,7 @@ class SmmPostController extends AdminDistController
      * 上传图片到本地
      */
     public function upload() {
-        $disk = $this->disk('local');
+        $disk = $this->disk('oss');
         // 获取上传的文件
         $file = $this->file();
         $dir = 'ssm/'.getDistributorId();
@@ -143,7 +138,6 @@ class SmmPostController extends AdminDistController
             : $this->responseErrorMessage(admin_trans_label('upload_failed'));
     }
 
-
     /*
      * 判断路径是否正确
      */
@@ -154,4 +148,85 @@ class SmmPostController extends AdminDistController
         }
         return false;
     }
+
+    private function stepLeaving($step,$index=0)
+    {
+        $lang = config('app.locale');//当前语言
+        //JS 验证参数不能为空
+        if ($index == 0) {
+$step->leaving(<<<JS
+function validateForm(formArray) {
+    lang = '{$lang}';
+    // 仅获取radio类型的post_type值
+    let postType = formArray.find(item =>
+        item.name === 'post_type' && item.type === 'radio'
+    )?.value;
+
+    // 仅获取radio类型的send_type值
+    let sendType = formArray.find(item =>
+        item.name === 'send_type' && item.type === 'radio'
+    )?.value;
+
+    // 验证post_type相关规则
+    if (postType === '0') {
+        const imageMessage = formArray.find(item => item.name === 'image_message')?.value;
+        const imageUrl = formArray.find(item => item.name === 'image_url')?.value;
+        if (!imageMessage || !imageUrl) {
+            if (lang === 'en') {
+                return 'Post message and images cannot be empty';
+            } else {
+                return '帖子留言和图片不能为空';
+            }
+        }
+    } else if (postType === '1') {
+        const videoMessage = formArray.find(item => item.name === 'video_message')?.value;
+        const videoUrl = formArray.find(item => item.name === 'video_url')?.value;
+        if (!videoMessage || !videoUrl) {
+            if (lang === 'en') {
+                return 'Post message and video cannot be empty';
+            } else {
+                return '帖子留言和视频不能为空';
+            }
+        }
+    }
+
+    // 验证send_type规则(仅处理radio类型的send_type)
+    if (sendType === '1') {
+        const sendTime = formArray.find(item => item.name === 'send_time')?.value;
+        if (!sendTime) {
+            if (lang === 'en') {
+                return 'Send time cannot be empty';
+            } else {
+                return '定时发送时间不能为空';
+            }
+        }
+    }
+    return "";
+}
+
+var formArray = args.formArray;
+rs = validateForm(formArray);
+if (rs != "") {
+    Dcat.error(rs);
+    return false;
+}
+JS);
+        } else {
+$step->leaving(<<<JS
+var formArray = args.formArray;
+var lang = '{$lang}';
+//找account_ids
+let accountIds = formArray.find(item => item.name === 'account_ids[]')?.value;
+if (!accountIds || accountIds.length === 0) {
+    if (lang === 'en') {
+        Dcat.error('Please select accounts');
+    } else {
+        Dcat.error('请选择社媒帐号');
+    }
+    return false;
+}
+JS);
+        }
+
+    }
 }

+ 103 - 0
app/Distributor/Controllers/SmmPostLogController.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace App\Distributor\Controllers;
+
+use App\Distributor\Repositories\SmmPostLog;
+use Dcat\Admin\Form;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use Dcat\Admin\Http\Controllers\AdminController;
+use Dcat\Admin\Layout\Content;
+use Dcat\Admin\Admin;
+
+class SmmPostLogController extends AdminController
+{
+    /**
+     * page index
+     */
+    public function index(Content $content)
+    {
+        return $content
+            ->header('列表')
+            ->description('全部')
+            ->breadcrumb(['text'=>'列表','url'=>''])
+            ->body($this->grid());
+    }
+
+    /**
+     * Make a grid builder.
+     *
+     * @return Grid
+     */
+    protected function grid()
+    {
+        return Grid::make(new SmmPostLog(), function (Grid $grid) {
+            $grid->column('id')->sortable();
+            $grid->column('post_id');
+            $grid->column('account_id');
+            $grid->column('status');
+            $grid->column('remark');
+            $grid->column('dist_id');
+            $grid->column('request_content');
+            $grid->column('response_content');
+            $grid->column('media_name');
+            $grid->column('response_ids');
+            $grid->column('created_at');
+            $grid->column('updated_at')->sortable();
+
+            $grid->filter(function (Grid\Filter $filter) {
+                $filter->equal('id');
+
+            });
+        });
+    }
+
+    /**
+     * Make a show builder.
+     *
+     * @param mixed $id
+     *
+     * @return Show
+     */
+    protected function detail($id)
+    {
+        return Show::make($id, new SmmPostLog(), function (Show $show) {
+            $show->field('id');
+            $show->field('post_id');
+            $show->field('account_id');
+            $show->field('status');
+            $show->field('remark');
+            $show->field('dist_id');
+            $show->field('request_content');
+            $show->field('response_content');
+            $show->field('media_name');
+            $show->field('response_ids');
+            $show->field('created_at');
+            $show->field('updated_at');
+        });
+    }
+
+    /**
+     * Make a form builder.
+     *
+     * @return Form
+     */
+    protected function form()
+    {
+        return Form::make(new SmmPostLog(), function (Form $form) {
+            $form->display('id');
+            $form->text('post_id');
+            $form->text('account_id');
+            $form->text('status');
+            $form->text('remark');
+            $form->text('dist_id');
+            $form->text('request_content');
+            $form->text('response_content');
+            $form->text('media_name');
+            $form->text('response_ids');
+
+            $form->display('created_at');
+            $form->display('updated_at');
+        });
+    }
+}

+ 48 - 15
app/Distributor/Controllers/SmmUserAccountController.php

@@ -5,6 +5,7 @@ namespace App\Distributor\Controllers;
 use App\Distributor\Actions\SmmAddAccount;
 use App\Distributor\Repositories\SmmUserAccount;
 use App\Services\SmmService;
+use Carbon\Carbon;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
 use Dcat\Admin\Show;
@@ -12,6 +13,7 @@ use Dcat\Admin\Http\Controllers\AdminController;
 use Dcat\Admin\Layout\Content;
 use Dcat\Admin\Admin;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Session;
 
 class SmmUserAccountController extends AdminDistController
 {
@@ -20,6 +22,11 @@ class SmmUserAccountController extends AdminDistController
      */
     public function index(Content $content)
     {
+//        $imgPath = storage_path('app/ssm/1/fc15428f68ea8157bb0c4ea80cfb38d0.png');
+//        $ssmService = new SmmService('facebook');
+//        $result = $ssmService->postImage('This is a test image', $imgPath);
+//
+//        exit;
         return $content
             ->header('帐号管理')
             ->description('全部')
@@ -34,15 +41,36 @@ class SmmUserAccountController extends AdminDistController
     protected function grid()
     {
         return Grid::make(new SmmUserAccount(), function (Grid $grid) {
-            $grid->column('id')->sortable();
-            $grid->column('name')->tree();
-            $grid->column('access_token');
-            $grid->column('created_at');
-            $grid->column('updated_at')->sortable();
+            //指定视图,去掉删除按钮
+            $grid->view('admin.grid.table');
+            $grid->disableRowSelector(); // 关键代码
+            $grid->column('id')->width(80);
+            $grid->column('name')->tree()->display(function ($name) {
+                $num = SmmUserAccount::returnChildCount($this->id);
+                if ($this->parent_id == 0) {
+                    if ($num > 0) {
+                        return $name . '<span class="badge" style="background:#21b978;margin-left:5px">'. $num. '</span>';
+                    }
+                }
+                return $name;
+            });
+            $grid->column('account_id');
+            $grid->column('expires_at');
+            $grid->column('created_at')->sortable();
+            $grid->column('status')->display(function ($name) {
+                if ($this->parent_id > 0) {
+                    //如果当前时间大于过期时间,则显示过期
+                    if (Carbon::now() > $this->expires_at) {
+                        return '<span  class="label" style="background-color:#f56c6c;color:#fff">过期</span>';
+                    } else {
+                        return '<span class="label" style="background-color:#21b978;color:#fff">正常</span>';
+                    }
+                }
+            });
             $grid->filter(function (Grid\Filter $filter) {
                 $filter->panel();
                 $filter->expand();
-                $filter->like('user_name')->width(2);
+                $filter->like('name')->width(2);
             });
             // 传入数组
             $grid->rightTools([
@@ -57,9 +85,16 @@ class SmmUserAccountController extends AdminDistController
                 if ($dist_id == 0) {
                     $actions->append(new SmmAddAccount());
                     $actions->disableDelete();
+                } else {
+                    if (Carbon::now() > $actions->row->expires_at) {
+                        $parentAccount = SmmUserAccount::getAccountById($actions->row->parent_id);
+                        $smmAddAccount = new SmmAddAccount();
+                        $smmAddAccount->setData(admin_trans_label('reauthorization'),$parentAccount->name);
+                        $actions->append($smmAddAccount);
+                    }
                 }
             });
-
+            $grid->disableBatchDelete();
             $grid->disableCreateButton();
             $grid->disableEditButton();
             $grid->disableViewButton();
@@ -67,7 +102,6 @@ class SmmUserAccountController extends AdminDistController
         });
     }
 
-
     /*
      * 社媒回调接口
      */
@@ -76,21 +110,20 @@ class SmmUserAccountController extends AdminDistController
         try {
             $ssmService = new SmmService($mediaName);
             $result = $ssmService->loginCallback($request);
-           // dd($result);
             if ($result['status']) {
-                $accessToken = $result['data']['access_token'];
-                $userName = $result['data']['user_name'];
-                $userId = $result['data']['user_id'];
-                SmmUserAccount::createAccountIfMediaExists($mediaName, $userId,$userName, $accessToken);
+                $accessToken = $result['data']['accessToken'];
+                $expiresAt = $result['data']['accessToken_expiresAt'];
+                $userName = $result['data']['userName'];
+                $userId = $result['data']['userId'];
+                SmmUserAccount::createAccountIfMediaExists($mediaName, $userId,$userName, $accessToken,$expiresAt);
                 return response()->json(['code' => 1, 'msg' => 'success']);
             } else {
                 return response()->json(['code' => 0,'msg' => $result['data']]);
             }
         } catch (\Exception $e) {
-            return response()->json(['code' => 0,'msg' => 'error'.$e->getMessage()]);
+            return response()->json(['code' => 0,'msg' => 'error:'.$e->getMessage()]);
         }
     }
 
 
-
 }

+ 23 - 1
app/Distributor/Repositories/SmmPost.php

@@ -24,11 +24,33 @@ class SmmPost extends EloquentRepository
         $model->send_type = $post['send_type'];
         $model->send_time = $sendTime;
         $model->message = $post['message'];
-        $model->media_type = $post['media_type'];
+        $model->post_type = $post['post_type'];
         $model->account_ids = implode(',',$post['account_ids']);
         $model->image_video_url = $imageVideoUrl;
         $model->status = 0;
+        $model->dist_id = getDistributorId();
         $model->save();
         return $model->id;
     }
+
+    /*
+     * 找出状态 0 的数据
+     */
+    public static function getWaitPost()
+    {
+        $model = new Model();
+        $model = $model->where('status',0)->get();
+        return $model;
+    }
+
+    public static function getPostById($id)
+    {
+        $model = new Model();
+        $model = $model->where('id',$id)->first();
+        return $model;
+    }
+
+
+
+
 }

+ 48 - 0
app/Distributor/Repositories/SmmPostLog.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Distributor\Repositories;
+
+use App\Models\SmmPostLog as Model;
+use Carbon\Carbon;
+use Dcat\Admin\Repositories\EloquentRepository;
+
+class SmmPostLog extends EloquentRepository
+{
+    /**
+     * Model.
+     *
+     * @var string
+     */
+    protected $eloquentClass = Model::class;
+
+    /*
+     * 创建日志
+     */
+    public static function createLog($data)
+    {
+        $log = new Model();
+        $log->post_id = $data['post_id'];
+        $log->account_id = $data['account_id'];
+        $log->account_name = $data['account_name'];
+        $log->status = $data['status'];
+        $log->remark = $data['remark'];
+        $log->created_at = $data['created_at'];
+        $log->updated_at = $data['updated_at'];
+        $log->dist_id = $data['dist_id'];
+        $log->media_name = $data['media_name'];
+        $log->response_ids = $data['response_ids'];
+        $log->send_time = $data['send_time'];
+        $log->save();
+    }
+
+    /*
+     * 找状态为0并且发送时间小于当前时间的日志
+     */
+    public static function getSendLog()
+    {
+        $log = new Model();
+        $logs = $log->where('status', 0)->where('send_time', '<', Carbon::now())->get();
+        return $logs;
+    }
+
+}

+ 58 - 14
app/Distributor/Repositories/SmmUserAccount.php

@@ -23,24 +23,36 @@ class SmmUserAccount extends EloquentRepository
      * @param string $accessToken 访问令牌
      * @return Model|null         新创建的模型实例或null
      */
-    public static function createAccountIfMediaExists($mediaName, $accountId,$accountName, $accessToken)
+    public static function createAccountIfMediaExists($mediaName, $accountId,$accountName, $accessToken,$expiresAt)
     {
         $model = new Model();
         // 查找匹配的社媒记录
-        $mediaRecord = $model->where('title', $mediaName)->first();
+        $mediaRecord = $model->where('name', $mediaName)->first();
         if (!$mediaRecord) {
             return null;
         }
-        // 创建新账号并关联父级
-        $data = [
-            'account_id'   => $accountId,
-            'title'        => $accountName,
-            'access_token' => $accessToken,
-            'parent_id'    => $mediaRecord->id,
-            'created_at'   => Carbon::now(),  // 自动生成时间戳
-            'updated_at'   => Carbon::now(),
-        ];
-        return $model->insert($data);
+        // 查找是否存在相同的账号
+        $userRow = $model->where('account_id', $accountId)->first();
+        if ($userRow) {
+            $userRow->access_token = $accessToken;
+            $userRow->expires_at = $expiresAt;
+            $userRow->name = $accountName;
+            $userRow->save();
+        } else {
+            // 创建新账号并关联父级
+            $data = [
+                'account_id'   => $accountId,
+                'name'        => $accountName,
+                'access_token' => $accessToken,
+                'expires_at'   => $expiresAt,
+                'parent_id'    => $mediaRecord->id,
+                'dist_id'      => getDistributorId(),
+                'created_at'   => Carbon::now(),  // 自动生成时间戳
+                'updated_at'   => Carbon::now(),
+            ];
+            $model->insert($data);
+        }
+        return true;
     }
 
 
@@ -56,14 +68,46 @@ class SmmUserAccount extends EloquentRepository
 
 
     /*
-     * 查找所有用户账号
+     * 查找所有用户账号(只显示有效的账号)
      */
     public static function getUserAccounts()
     {
         $model = new Model();
-        $accounts = $model->where('dist_id','>', 0)->where('parent_id','>',0)->orderBy('parent_id', 'asc')->get();
+        $accounts = $model->where('dist_id','>', 0)->where('parent_id','>',0)->where('expires_at','>', Carbon::now())->orderBy('parent_id', 'asc')->get();
+        return $accounts;
+    }
+
+    /*
+     *  查找所有用户账号
+     */
+    public static function getAccountsByIds($ids)
+    {
+        $model = new Model();
+        $accounts = $model->whereIn('id', $ids)->get();
         return $accounts;
     }
 
+    public static function getAccountById($id)
+    {
+        $model = new Model();
+        $account = $model->find($id);
+        return $account;
+    }
+
+    /*
+     * 返回子账号数量
+     */
+    public static function returnChildCount($id)
+    {
+        $model = new Model();
+        $count = $model->where('parent_id', $id)->count();
+        return $count;
+    }
+
+
+
+
+
+
 
 }

+ 4 - 11
app/Distributor/routes.php

@@ -80,9 +80,12 @@ Route::group([
     $router->get('ssm-user-account', 'SmmUserAccountController@index');
     //社媒传单管理
     $router->resource('ssm-post', 'SmmPostController');
-
+    //社媒回调
+    $router->get('callback/{name}', 'SmmUserAccountController@callback');
 });
 
+
+
 /*
  * 重写上传路由,修改tinymce上传路径
  */
@@ -98,13 +101,3 @@ app('router')->group($attributes, function ($router) {
 
 
 
-/*
- * 对外开放的接口
- */
-Route::group([
-    'prefix'     => 'open',
-    'namespace'  => config('admin.route.namespace'),
-], function (Router $router) {
-    //社媒回调
-    $router->get('callback/{name}', 'SmmUserAccountController@callback');
-});

+ 16 - 0
app/Models/SmmPostLog.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Dcat\Admin\Traits\HasDateTimeFormatter;
+
+use Illuminate\Database\Eloquent\Model;
+
+class SmmPostLog extends Model
+{
+	use HasDateTimeFormatter;
+    protected $table = 'smm_post_log';
+
+
+
+}

+ 2 - 0
app/Models/SmmUserAccount.php

@@ -23,4 +23,6 @@ class SmmUserAccount extends Model
         return $this->hasOne(SmmUserAccount::class,'id','parent_id');
     }
 
+
+
 }

+ 2 - 2
app/Services/Contracts/SmmPlatformInterface.php

@@ -51,7 +51,7 @@ interface SmmPlatformInterface
      *  'data' => '发布失败'
      * ]
      */
-    public function postImage($message,$imagePath);
+    public function postImage($message,$imagePaths,$accessToken);
 
     /*
      * 发布视频帖子
@@ -64,7 +64,7 @@ interface SmmPlatformInterface
      *  'image_video_url' => 'https://example.com/video.mp4', //帖子图片URL
      * ]
      */
-    public function postVideo($message,$videoPath);
+    public function postVideo($message,$videoPath,$accessToken);
 
     /*
      * 获取帖评论列表

+ 186 - 52
app/Services/Smm/FacebookService.php

@@ -2,20 +2,29 @@
 
 namespace App\Services\Smm;
 use App\Services\Contracts\SmmPlatformInterface;
+use Carbon\Carbon;
 use Illuminate\Http\Request;
-use Facebook\Facebook;
+use \Facebook\Facebook;
+use Illuminate\Support\Facades\Session;
 
 
+use \Facebook\Exceptions\FacebookResponseException;
+use \Facebook\Exceptions\FacebookSDKException;
+
 class FacebookService implements SmmPlatformInterface
 {
     public $fb = null;
     public $pageAccessToken = null;
 
-    public function __construct() {
+    // 构造函数,传入配置信息
+    public $configData = [];
+
+    public function __construct($configData) {
+        $this->configData = $configData;
         $this->fb = new Facebook([
             'app_id' => env('SSM_FACEBOOK_APP_ID'), // 替换为您的App ID
             'app_secret' => env('SSM_FACEBOOK_APP_SECRET'), // 替换为您的App Secret
-            'default_graph_version' => 'v22.0', // 使用当前版本
+            'default_graph_version' => 'v19.0', // 使用当前版本
         ]);
         //$this->pageAccessToken = $pageAccessToken;
     }
@@ -26,10 +35,24 @@ class FacebookService implements SmmPlatformInterface
      */
     public function login()
     {
+        session_start();
         // 实现Facebook登录逻辑
         $helper = $this->fb->getRedirectLoginHelper();
-        $permissions = ['public_profile','email'];
-        $loginUrl = $helper->getLoginUrl(env('DIST_SITE_URL').'/open/callback/facebook', $permissions);
+        $permissions = [
+            'publish_video',
+            'pages_manage_cta',
+            'pages_show_list',
+            'business_management',
+            'pages_read_engagement',
+            'pages_manage_metadata',
+            'pages_read_user_content',
+            'pages_manage_ads',
+            'pages_manage_posts',
+            'pages_manage_engagement',
+        ];
+
+        $loginUrl = $helper->getLoginUrl(env('DIST_SITE_URL').'/dist/callback/facebook', $permissions);
+
         return ['status'=>true, 'data' => ['url'=>$loginUrl]];
     }
 
@@ -38,12 +61,14 @@ class FacebookService implements SmmPlatformInterface
      * 授权成功后,得到access_token,refresh_token等信息, 保存到数据库中
      * 授权成功后,返回回调需要的数据
      */
+
+
     public function loginCallback(Request $request)
     {
-        // 实现Facebook回调处理
         $helper = $this->fb->getRedirectLoginHelper();
-        // Validate the state parameter
+
         if (isset($_GET['state'])) {
+            // 由于session的原因,这里需要重新设置state
             $helper->getPersistentDataHandler()->set('state', $_GET['state']);
         }
         try {
@@ -57,25 +82,85 @@ class FacebookService implements SmmPlatformInterface
             // 可选:将短期令牌转换为长期令牌(有效期约60天)
             $oAuth2Client = $this->fb->getOAuth2Client();
             $longLivedAccessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
-            $useriInfo = $this->getFacebookUser($longLivedAccessToken);
-            return ['status' => true, 'data' => ['access_token' => $longLivedAccessToken,'user_name'=>$useriInfo['name'],'user_id'=>$useriInfo['id']]];
+            $useriInfo = $this->getFacebookUser($longLivedAccessToken->getValue());
+            if ($useriInfo['status'] == false) {
+                return ['status' => false, 'data' => $useriInfo['data']];
+            }
+            $expiresAt = $longLivedAccessToken->getExpiresAt();
+            //DateTime转 carbon
+            if ($expiresAt != null) {
+                $expiresAt = $expiresAt->format('Y-m-d H:i:s');
+            } else {
+                $expiresAt = Carbon::now()->addDays(60)->format('Y-m-d H:i:s');
+            }
+            //$expiresAt = $expiresAt->format('Y-m-d H:i:s');
+            //保存到数据库中
+            return ['status' => true, 'data' => ['accessToken' => $longLivedAccessToken->getValue(),'accessToken_expiresAt'=>$expiresAt,'userName'=>$useriInfo['data']['name'],'userId'=>$useriInfo['data']['id']]];
         } else {
             return ['status' => false, 'data' => '无法获取访问令牌'];
         }
     }
 
-    public function postImage($message,$imagePath) {
+    /*
+     * 发布图片,可以发多个图片
+     * $imagePath = ['/path/to/image1.jpg','/path/to/image2.jpg'];
+     */
+    public function postImage($message,$imagePaths,$accessToken) {
+        // 获取pageAccessToken
+        $info = $this->getAccountsInfo($accessToken);
+        if ($info['status'] == false) {
+            return ['status' => false, 'data' => $info['data']];
+        }
+
         try {
-            $response = $this->fb->post(
-                '/me/photos',
-                [
-                    'source' => $this->fb->fileToUpload($imagePath), // 上传图片文件
-                    'message' => $message,                    // 添加描述
-                ],
-                $this->userAccessToken
-            );
-            $graphNode = $response->getGraphNode();
-            return ['status' => true, 'data' => ['id' => $graphNode['id']]];
+            $requestContent = [];
+            $responseContent = [];
+            $postIds = [];
+            foreach ($info['data'] as $value) {
+                $mediaIds  = [];
+                $pageId = $value['id'];
+                // 第一步:上传每张图片,获取 media_id
+                foreach ($imagePaths as $imagePath){
+                    //dd($imagePath);
+                    $pageAccessToken = $value['pageAccessToken'];
+                    $postData = [
+                        'source' => $this->fb->fileToUpload($imagePath), // 上传图片文件
+                      //  'source' => $imagePath, // 上传图片文件
+                        'published' => false,                    // 添加描述
+                    ];
+                    $postUrl = '/'.$pageId.'/photos';
+
+                    $response = $this->fb->post(
+                        $postUrl,
+                        $postData,
+                        $pageAccessToken
+                    );
+                    $requestContent[] = ['postUrl'=>$postUrl,'data'=>$postData,'pageAccessToken'=>$pageAccessToken];
+                    $responseContent[] = $response->getDecodedBody();
+                    $mediaIds[] = $response->getDecodedBody()['id'];
+                }
+
+                // 第二步:发布帖子,附加多个 media_id
+                $attachedMedia = array_map(function($id) {
+                    return ['media_fbid' => $id];
+                }, $mediaIds);
+
+                $feedParams = [
+                    'message' => $message,
+                    'attached_media' => json_encode($attachedMedia)
+                ];
+
+                $postUrl = '/'.$pageId.'/feed';
+
+                $response = $this->fb->post(
+                    $postUrl,
+                    $feedParams, $pageAccessToken
+                );
+                $requestContent[] = ['postUrl'=>$postUrl,'data'=>$feedParams,'pageAccessToken'=>$pageAccessToken];
+                $responseContent[] = $response->getDecodedBody();
+                $postIds[] = $response->getDecodedBody()['id'];
+            }
+            return ['status' => true, 'data' => ['responseIds' => $postIds,'requestContent'=>$requestContent,'responseContent'=>$responseContent]];
         } catch (Facebook\Exceptions\FacebookResponseException $e) {
             $errorMsg  = 'Graph API错误:' . $e->getMessage();
         } catch (Facebook\Exceptions\FacebookSDKException $e) {
@@ -85,19 +170,36 @@ class FacebookService implements SmmPlatformInterface
     }
 
 
-    public function postVideo($message,$videoPath) {
+    /*
+     * 发布视频,只能发一个视频
+     */
+    public function postVideo($message,$videoPath,$accessToken) {
+        $info = $this->getAccountsInfo($accessToken);
+        if ($info['status'] == false) {
+            return ['status' => false, 'data' => $info['data']];
+        }
         try {
-            $response = $this->fb->post(
-                '/me/videos',
-                [
-                    'source' => $this->fb->fileToUpload($videoPath), // 上传视频文件
-                    'title' => $message,                        // 添加标题
-                    'description' => '',            // 添加描述
-                ],
-                $this->userAccessToken
-            );
-            $graphNode = $response->getGraphNode();
-            return ['status' => true, 'data' => ['id' => $graphNode['id']]];
+            $postIds = [];
+            $requestContent = [];
+            $responseContent = [];
+            foreach ($info['data'] as $value) {
+                $pageId = $value['id'];
+                $pageAccessToken = $value['pageAccessToken'];
+                $data = [
+                    'source' => $this->fb->fileToUpload($videoPath), // 上传图片文件
+                    'description' => $message,                     // 添加描述
+                ];
+                $response = $this->fb->post(
+                    '/'.$pageId.'/videos',
+                    $data,
+                    $pageAccessToken
+                );
+                $requestContent[] = ['url'=>'/'.$pageId.'/videos','data'=>$data,'pageAccessToken'=>$pageAccessToken];
+                $responseContent[] = $response->getDecodedBody();
+                $postIds[] = $response->getDecodedBody()['id'];
+                //第二步:发布帖子,附加 media_id
+            }
+            return ['status' => true, 'data' => ['responseIds' => $postIds,'requestContent'=>$requestContent,'responseContent'=>$responseContent]];
         } catch (Facebook\Exceptions\FacebookResponseException $e) {
             $errorMsg = 'Graph API错误:' . $e->getMessage();
         } catch (Facebook\Exceptions\FacebookSDKException $e) {
@@ -133,40 +235,72 @@ class FacebookService implements SmmPlatformInterface
         try {
             // 验证并设置访问令牌
             $this->fb->setDefaultAccessToken($accessToken);
-
             // 发送请求获取用户信息
             $response = $this->fb->get('/me?fields=name,id');
             $userNode = $response->getGraphUser();
-
             return [
-                'name' => $userNode->getName(),
-                'id' => $userNode->getId(),
-                'error' => null
+                'status' => true,
+                'data' => [
+                    'name' => $userNode->getName(),
+                    'id' => $userNode->getId(),
+                ]
             ];
-
         } catch (FacebookResponseException $e) {
             // API 响应错误处理
-            return [
-                'name' => null,
-                'id' => null,
-                'error' => 'Graph API Error: ' . $e->getMessage()
-            ];
+            $errorMsg = 'Graph API Error: ' . $e->getMessage();
         } catch (FacebookSDKException $e) {
             // SDK 错误处理
-            return [
-                'name' => null,
-                'id' => null,
-                'error' => 'SDK Error: ' . $e->getMessage()
-            ];
+            $errorMsg = 'SDK Error: ' . $e->getMessage();
         } catch (Exception $e) {
             // 其他错误处理
+            $errorMsg = 'General Error: ' . $e->getMessage();
+        }
+        return [
+            'status' => false,
+            'data' => $errorMsg
+        ];
+    }
+
+
+    public function getAccountsInfo($accessToken) {
+        try {
+            // 验证并设置访问令牌
+            $this->fb->setDefaultAccessToken($accessToken);
+            // 发送请求获取用户信息
+            $response = $this->fb->get('/me/accounts');
+            $userNode = $response->getDecodedBody();
+            if ($userNode['data'] == [] || $userNode['data'] == null || $userNode['data'] == '') {
+                return [
+                    'status' => false,
+                    'data' => '没有获取到任何账号信息'
+                ];
+            }
+            $data = [];
+            foreach ($userNode['data'] as $value) {
+                $data[] = [
+                    'id' => $value['id'],
+                    'name' => $value['name'],
+                    'pageAccessToken' => $value['access_token'],
+                ];
+            }
             return [
-                'name' => null,
-                'id' => null,
-                'error' => 'General Error: ' . $e->getMessage()
+                'status'=>true,
+                'data' => $data
             ];
+        } catch (FacebookResponseException $e) {
+            // API 响应错误处理
+            $errorMsg = 'Graph API Error: ' . $e->getMessage();
+        } catch (FacebookSDKException $e) {
+            // SDK 错误处理
+            $errorMsg = 'SDK Error: ' . $e->getMessage();
+        } catch (Exception $e) {
+            // 其他错误处理
+            $errorMsg = 'General Error: ' . $e->getMessage();
         }
+        return [
+            'status'=>false,
+            'data' => $errorMsg
+        ];
     }
 
-
 }

+ 319 - 0
app/Services/Smm/InstagramService.php

@@ -0,0 +1,319 @@
+<?php
+
+namespace App\Services\Smm;
+use App\Services\Contracts\SmmPlatformInterface;
+use Carbon\Carbon;
+use Facebook\Exceptions\FacebookResponseException;
+use Facebook\Exceptions\FacebookSDKException;
+use Facebook\Facebook;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Http;
+
+
+class InstagramService implements SmmPlatformInterface
+{
+    protected $clientId;
+    protected $clientSecret;
+    protected $redirectUri;
+    // 构造函数,传入配置信息
+    public $configData = [];
+
+    public function __construct($configData) {
+        $this->configData = $configData;
+        $this->clientId = env('SSM_INSTAGRAM_APP_ID');
+        $this->clientSecret = env('SSM_INSTAGRAM_APP_SECRET');
+        $this->redirectUri = env('DIST_SITE_URL').'/dist/callback/instagram';
+    }
+
+    /*
+     * OAuth 2.0 授权登录
+     * 返回授权地址:https://example.com/fb-callback.php
+     */
+    public function login()
+    {
+        $loginUrl = 'https://www.instagram.com/oauth/authorize' . '?' . http_build_query([
+                'enable_fb_login'=>0,
+                'force_authentication'=>1,
+                'client_id' => $this->clientId,
+                'redirect_uri' => $this->redirectUri,
+                'scope' => 'instagram_business_basic,instagram_business_manage_messages,instagram_business_manage_comments,instagram_business_content_publish,instagram_business_manage_insights',
+                'response_type' => 'code',
+            ]);
+        return ['status'=>true, 'data' => ['url'=>$loginUrl]];
+    }
+
+    /*
+     * OAuth 2.0 授权回调
+     * 授权成功后,得到access_token,refresh_token等信息, 保存到数据库中
+     * 授权成功后,返回回调需要的数据
+     */
+
+
+    public function loginCallback(Request $request)
+    {
+        if (!$request->has('code')) {
+            return ['status' => false, 'data' => '未收到授权代码'];
+        }
+        $code = $request->input('code');
+        // 1. 交换授权代码以获取短期访问令牌
+        $tokenResponse = Http::asForm()->post('https://api.instagram.com/oauth/access_token', [
+            'client_id' => $this->clientId,
+            'client_secret' => $this->clientSecret,
+            'grant_type' => 'authorization_code',
+            'redirect_uri' => $this->redirectUri,
+            'code' => $code,
+        ]);
+        $tokenData = $tokenResponse->json();
+        if (!isset($tokenData['access_token'])) {
+            return ['status' => false, 'data' => '错误:无法获取短期访问令牌 - ' ];
+        }
+        $shortLivedToken = $tokenData['access_token'];
+        // 2. 使用短期令牌交换长期令牌
+        $longLivedTokenResponse = Http::get('https://graph.instagram.com/access_token', [
+            'grant_type' => 'ig_exchange_token',
+            'client_secret' => $this->clientSecret,
+            'access_token' => $shortLivedToken,
+        ]);
+        $longLivedTokenData = $longLivedTokenResponse->json();
+        if (!isset($longLivedTokenData['access_token'])) {
+            return ['status' => false, 'data' => '错误:无法获取长期访问令牌 - ' ];
+        }
+        $accessToken = $longLivedTokenData['access_token'];
+        // 3. 获取用户信息
+        $userResponse = Http::get('https://graph.instagram.com/me', [
+            'fields' => 'id,username',
+            'access_token' => $accessToken,
+        ]);
+        $userInfo = $userResponse->json();
+        if (!isset($userInfo['id']) || !isset($userInfo['username'])) {
+            return ['status' => false, 'data' => '错误:无法获取用户信息 - '];
+        }
+        $expiresInSeconds = $longLivedTokenData['expires_in'];
+        // 当前时间
+        $now = Carbon::now();
+        $expiresAt = $now->copy()->addSeconds($expiresInSeconds);
+        // 返回用户信息和长期访问令牌
+        return ['status' => true, 'data' => [
+            'accessToken' => $accessToken,
+            'accessToken_expiresAt'=>$expiresAt,
+            'userName'=>$userInfo['username'],'userId'=>$userInfo['id']
+        ]];
+    }
+
+    /*
+         * 发布图片,可以发多个图片
+         * $imagePaths = ['/path/to/image1.jpg','/path/to/image2.jpg'];
+         */
+    public function postImage($message, $imagePaths, $accessToken)
+    {
+        try {
+            if (empty($imagePaths)) {
+                return ['status' => false, 'data' => '错误:未提供图片路径'];
+            }
+
+            $igUserId = $this->configData['accountInfo']['account_id'];
+
+            // If single image
+            if (count($imagePaths) === 1) {
+                $imagePath = $imagePaths[0];
+                // Create media container for single image
+                $postData = [
+                    'image_url' => $imagePath, // Assuming images are publicly accessible
+                    'caption' => $message,
+                    'access_token' => $accessToken,
+                ];
+                $containerResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData);
+
+                $containerData = $containerResponse->json();
+
+
+                if (!isset($containerData['id'])) {
+                    return ['status' => false, 'data' => '错误:无法创建媒体容器 - ' . json_encode($containerData)];
+                }
+
+                // Publish the container
+                $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", [
+                    'creation_id' => $containerData['id'],
+                    'access_token' => $accessToken,
+                ]);
+
+                $publishData = $publishResponse->json();
+                if (!isset($publishData['id'])) {
+                    return ['status' => false, 'data' => '错误:无法发布图片 - ' . json_encode($publishData)];
+                }
+                $postIds = [$publishData['id']];
+                $requestContent = [$postData];
+                $responseContent = [$publishData];
+
+                return ['status' => true,
+                    'data' => [
+                        'responseIds' => $postIds,
+                        'requestContent' => $requestContent,
+                        'responseContent' => $responseContent,
+                    ]];
+            }
+
+            // For multiple images (carousel post)
+            $children = [];
+            $requestContent = [];
+            $responseContent = [];
+            foreach ($imagePaths as $imagePath) {
+                $postData = [
+                    'image_url' => asset($imagePath),
+                    'is_carousel_item' => true,
+                    'access_token' => $accessToken,
+                ];
+                $requestContent[] = [
+                    'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
+                    'postData' => $postData,
+                ];
+                $childResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media", $postData);
+
+                $childData = $childResponse->json();
+                $responseContent[] = $childData;
+                if (!isset($childData['id'])) {
+                    return ['status' => false, 'data' => '错误:无法创建子媒体容器 - ' . json_encode($childData)];
+                }
+                $children[] = $childData['id'];
+            }
+            // Create carousel container
+            $postData = [
+                'media_type' => 'CAROUSEL',
+                'children' => implode(',', $children),
+                'caption' => $message,
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = [
+                'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
+                'postData' => $postData,
+            ];
+            $carouselResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData );
+
+            $carouselData = $carouselResponse->json();
+            $responseContent[] = $carouselData;
+            if (!isset($carouselData['id'])) {
+                return ['status' => false, 'data' => '错误:无法创建轮播容器 - ' . json_encode($carouselData)];
+            }
+            // Publish carousel
+            $postData = [
+                'creation_id' => $carouselData['id'],
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = [
+                'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media_publish",
+                'postData' => $postData,
+            ];
+            $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", $postData);
+
+            $publishData = $publishResponse->json();
+            $responseContent[] = $publishData;
+            if (!isset($publishData['id'])) {
+                return ['status' => false, 'data' => '错误:无法发布轮播 - ' . json_encode($publishData)];
+            }
+            $postIds = [$publishData['id']];
+            return ['status' => true,
+                'data' => [
+                    'responseIds' => $postIds,
+                    'requestContent' => $requestContent,
+                    'responseContent' => $responseContent,
+                ]];
+        } catch (\Exception $e) {
+            //Log::error('Instagram postImage error: ' . $e->getMessage());
+            return ['status' => false, 'data' => '错误:' . $e->getMessage()];
+        }
+    }
+
+    /*
+     * 发布视频,只能发一个视频
+     */
+    public function postVideo($message, $videoPath, $accessToken)
+    {
+        try {
+            if (empty($videoPath)) {
+                return ['status' => false, 'data' => '错误:未提供视频路径'];
+            }
+            $igUserId = $this->configData['accountInfo']['account_id'];
+
+            $requestContent = [];
+            $responseContent = [];
+
+            // Create media container for video
+            $postData = [
+                'media_type' => 'REELS',
+                'video_url' => $videoPath, // Assuming video is publicly accessible
+                'caption' => $message,
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = ['url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media", 'postData' => $postData];
+            $containerResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media", $postData);
+            $containerData = $containerResponse->json();
+            if (!isset($containerData['id'])) {
+                return ['status' => false, 'data' => '错误:无法创建视频容器 - ' . json_encode($containerData)];
+            }
+            $responseContent[] = $containerData;
+            // Check container status (video processing may take time)
+            $status = 'IN_PROGRESS';
+            $maxAttempts = 10;
+            $attempt = 0;
+            while ($status === 'IN_PROGRESS' && $attempt < $maxAttempts) {
+                sleep(5); // Wait 5 seconds between checks
+                $postData = [
+                    'fields' => 'status',
+                    'access_token' => $accessToken,
+                ];
+                $requestContent[] = ['url'=>"https://graph.instagram.com/{$containerData['id']}", 'postData' => $postData];
+                $statusResponse = Http::get("https://graph.instagram.com/{$containerData['id']}", $postData);
+
+                $statusData = $statusResponse->json();
+                $status = $statusData['status'] ?? 'ERROR';
+                $attempt++;
+
+                if ($status === 'ERROR') {
+                    return ['status' => false, 'data' => '错误:视频处理失败'];
+                }
+                $responseContent[] = $statusData;
+            }
+            if ($status !== 'FINISHED') {
+                return ['status' => false, 'data' => '错误:视频处理超时'];
+            }
+            // Publish the video
+            $postData =  [
+                'creation_id' => $containerData['id'],
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = ['url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media_publish", 'postData' => $postData];
+            $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish",$postData);
+
+            $publishData = $publishResponse->json();
+            if (!isset($publishData['id'])) {
+                return ['status' => false, 'data' => '错误:无法发布视频 - ' . json_encode($publishData)];
+            }
+            $responseContent[] = $publishData;
+            $postIds = [$publishData['id']];
+            return ['status' => true,
+                'data' => [
+                    'responseIds' => $postIds,
+                    'requestContent' => $requestContent,
+                    'responseContent' => $responseContent,
+                ]];
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => '错误:' . $e->getMessage()];
+        }
+    }
+
+
+    public function getComments($postId) {
+
+    }
+
+    public function replyToComment($commentId) {
+
+    }
+
+    public function deleteComment($commentId) {
+
+    }
+
+
+
+}

+ 314 - 0
app/Services/Smm/YoutubeService.php

@@ -0,0 +1,314 @@
+<?php
+
+namespace App\Services\Smm;
+use App\Services\Contracts\SmmPlatformInterface;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Http;
+
+
+class YoutubeService implements SmmPlatformInterface
+{
+    protected $clientId;
+    protected $clientSecret;
+    // 构造函数,传入配置信息
+    public $configData = [];
+
+    public function __construct($configData) {
+        $this->configData = $configData;
+        $this->clientId = env('SSM_INSTAGRAM_APP_ID');
+        $this->clientSecret = env('SSM_INSTAGRAM_APP_SECRET');
+    }
+
+    /*
+     * OAuth 2.0 授权登录
+     * 返回授权地址:https://example.com/fb-callback.php
+     */
+    public function login()
+    {
+        $loginUrl = 'https://www.instagram.com/oauth/authorize' . '?' . http_build_query([
+                'enable_fb_login'=>0,
+                'force_authentication'=>1,
+                'client_id' => $this->clientId,
+                'redirect_uri' => $this->redirectUri,
+                'scope' => 'instagram_business_basic,instagram_business_manage_messages,instagram_business_manage_comments,instagram_business_content_publish,instagram_business_manage_insights',
+                'response_type' => 'code',
+            ]);
+        return ['status'=>true, 'data' => ['url'=>$loginUrl]];
+    }
+
+    /*
+     * OAuth 2.0 授权回调
+     * 授权成功后,得到access_token,refresh_token等信息, 保存到数据库中
+     * 授权成功后,返回回调需要的数据
+     */
+
+
+    public function loginCallback(Request $request)
+    {
+        if (!$request->has('code')) {
+            return ['status' => false, 'data' => '未收到授权代码'];
+        }
+        $code = $request->input('code');
+        // 1. 交换授权代码以获取短期访问令牌
+        $tokenResponse = Http::asForm()->post('https://api.instagram.com/oauth/access_token', [
+            'client_id' => $this->clientId,
+            'client_secret' => $this->clientSecret,
+            'grant_type' => 'authorization_code',
+            'redirect_uri' => $this->redirectUri,
+            'code' => $code,
+        ]);
+        $tokenData = $tokenResponse->json();
+        if (!isset($tokenData['access_token'])) {
+            return ['status' => false, 'data' => '错误:无法获取短期访问令牌 - ' ];
+        }
+        $shortLivedToken = $tokenData['access_token'];
+        // 2. 使用短期令牌交换长期令牌
+        $longLivedTokenResponse = Http::get('https://graph.instagram.com/access_token', [
+            'grant_type' => 'ig_exchange_token',
+            'client_secret' => $this->clientSecret,
+            'access_token' => $shortLivedToken,
+        ]);
+        $longLivedTokenData = $longLivedTokenResponse->json();
+        if (!isset($longLivedTokenData['access_token'])) {
+            return ['status' => false, 'data' => '错误:无法获取长期访问令牌 - ' ];
+        }
+        $accessToken = $longLivedTokenData['access_token'];
+        // 3. 获取用户信息
+        $userResponse = Http::get('https://graph.instagram.com/me', [
+            'fields' => 'id,username',
+            'access_token' => $accessToken,
+        ]);
+        $userInfo = $userResponse->json();
+        if (!isset($userInfo['id']) || !isset($userInfo['username'])) {
+            return ['status' => false, 'data' => '错误:无法获取用户信息 - '];
+        }
+        $expiresInSeconds = $longLivedTokenData['expires_in'];
+        // 当前时间
+        $now = Carbon::now();
+        $expiresAt = $now->copy()->addSeconds($expiresInSeconds);
+        // 返回用户信息和长期访问令牌
+        return ['status' => true, 'data' => [
+            'accessToken' => $accessToken,
+            'accessToken_expiresAt'=>$expiresAt,
+            'userName'=>$userInfo['username'],'userId'=>$userInfo['id']
+        ]];
+    }
+
+    /*
+         * 发布图片,可以发多个图片
+         * $imagePaths = ['/path/to/image1.jpg','/path/to/image2.jpg'];
+         */
+    public function postImage($message, $imagePaths, $accessToken)
+    {
+        try {
+            if (empty($imagePaths)) {
+                return ['status' => false, 'data' => '错误:未提供图片路径'];
+            }
+
+            $igUserId = $this->configData['accountInfo']['account_id'];
+
+            // If single image
+            if (count($imagePaths) === 1) {
+                $imagePath = $imagePaths[0];
+                // Create media container for single image
+                $postData = [
+                    'image_url' => $imagePath, // Assuming images are publicly accessible
+                    'caption' => $message,
+                    'access_token' => $accessToken,
+                ];
+                $containerResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData);
+
+                $containerData = $containerResponse->json();
+
+
+                if (!isset($containerData['id'])) {
+                    return ['status' => false, 'data' => '错误:无法创建媒体容器 - ' . json_encode($containerData)];
+                }
+
+                // Publish the container
+                $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", [
+                    'creation_id' => $containerData['id'],
+                    'access_token' => $accessToken,
+                ]);
+
+                $publishData = $publishResponse->json();
+                if (!isset($publishData['id'])) {
+                    return ['status' => false, 'data' => '错误:无法发布图片 - ' . json_encode($publishData)];
+                }
+                $postIds = [$publishData['id']];
+                $requestContent = [$postData];
+                $responseContent = [$publishData];
+
+                return ['status' => true,
+                    'data' => [
+                        'responseIds' => $postIds,
+                        'requestContent' => $requestContent,
+                        'responseContent' => $responseContent,
+                    ]];
+            }
+
+            // For multiple images (carousel post)
+            $children = [];
+            $requestContent = [];
+            $responseContent = [];
+            foreach ($imagePaths as $imagePath) {
+                $postData = [
+                    'image_url' => asset($imagePath),
+                    'is_carousel_item' => true,
+                    'access_token' => $accessToken,
+                ];
+                $requestContent[] = [
+                    'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
+                    'postData' => $postData,
+                ];
+                $childResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media", $postData);
+
+                $childData = $childResponse->json();
+                $responseContent[] = $childData;
+                if (!isset($childData['id'])) {
+                    return ['status' => false, 'data' => '错误:无法创建子媒体容器 - ' . json_encode($childData)];
+                }
+                $children[] = $childData['id'];
+            }
+            // Create carousel container
+            $postData = [
+                'media_type' => 'CAROUSEL',
+                'children' => implode(',', $children),
+                'caption' => $message,
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = [
+                'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media",
+                'postData' => $postData,
+            ];
+            $carouselResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media",$postData );
+
+            $carouselData = $carouselResponse->json();
+            $responseContent[] = $carouselData;
+            if (!isset($carouselData['id'])) {
+                return ['status' => false, 'data' => '错误:无法创建轮播容器 - ' . json_encode($carouselData)];
+            }
+            // Publish carousel
+            $postData = [
+                'creation_id' => $carouselData['id'],
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = [
+                'url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media_publish",
+                'postData' => $postData,
+            ];
+            $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish", $postData);
+
+            $publishData = $publishResponse->json();
+            $responseContent[] = $publishData;
+            if (!isset($publishData['id'])) {
+                return ['status' => false, 'data' => '错误:无法发布轮播 - ' . json_encode($publishData)];
+            }
+            $postIds = [$publishData['id']];
+            return ['status' => true,
+                'data' => [
+                    'responseIds' => $postIds,
+                    'requestContent' => $requestContent,
+                    'responseContent' => $responseContent,
+                ]];
+        } catch (\Exception $e) {
+            //Log::error('Instagram postImage error: ' . $e->getMessage());
+            return ['status' => false, 'data' => '错误:' . $e->getMessage()];
+        }
+    }
+
+    /*
+     * 发布视频,只能发一个视频
+     */
+    public function postVideo($message, $videoPath, $accessToken)
+    {
+        try {
+            if (empty($videoPath)) {
+                return ['status' => false, 'data' => '错误:未提供视频路径'];
+            }
+            $igUserId = $this->configData['accountInfo']['account_id'];
+
+            $requestContent = [];
+            $responseContent = [];
+
+            // Create media container for video
+            $postData = [
+                'media_type' => 'REELS',
+                'video_url' => $videoPath, // Assuming video is publicly accessible
+                'caption' => $message,
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = ['url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media", 'postData' => $postData];
+            $containerResponse = Http::asForm()->post("https://graph.instagram.com/v21.0/{$igUserId}/media", $postData);
+            $containerData = $containerResponse->json();
+            if (!isset($containerData['id'])) {
+                return ['status' => false, 'data' => '错误:无法创建视频容器 - ' . json_encode($containerData)];
+            }
+            $responseContent[] = $containerData;
+            // Check container status (video processing may take time)
+            $status = 'IN_PROGRESS';
+            $maxAttempts = 10;
+            $attempt = 0;
+            while ($status === 'IN_PROGRESS' && $attempt < $maxAttempts) {
+                sleep(5); // Wait 5 seconds between checks
+                $postData = [
+                    'fields' => 'status',
+                    'access_token' => $accessToken,
+                ];
+                $requestContent[] = ['url'=>"https://graph.instagram.com/{$containerData['id']}", 'postData' => $postData];
+                $statusResponse = Http::get("https://graph.instagram.com/{$containerData['id']}", $postData);
+
+                $statusData = $statusResponse->json();
+                $status = $statusData['status'] ?? 'ERROR';
+                $attempt++;
+
+                if ($status === 'ERROR') {
+                    return ['status' => false, 'data' => '错误:视频处理失败'];
+                }
+                $responseContent[] = $statusData;
+            }
+            if ($status !== 'FINISHED') {
+                return ['status' => false, 'data' => '错误:视频处理超时'];
+            }
+            // Publish the video
+            $postData =  [
+                'creation_id' => $containerData['id'],
+                'access_token' => $accessToken,
+            ];
+            $requestContent[] = ['url'=>"https://graph.instagram.com/v21.0/{$igUserId}/media_publish", 'postData' => $postData];
+            $publishResponse = Http::post("https://graph.instagram.com/v21.0/{$igUserId}/media_publish",$postData);
+
+            $publishData = $publishResponse->json();
+            if (!isset($publishData['id'])) {
+                return ['status' => false, 'data' => '错误:无法发布视频 - ' . json_encode($publishData)];
+            }
+            $responseContent[] = $publishData;
+            $postIds = [$publishData['id']];
+            return ['status' => true,
+                'data' => [
+                    'responseIds' => $postIds,
+                    'requestContent' => $requestContent,
+                    'responseContent' => $responseContent,
+                ]];
+        } catch (\Exception $e) {
+            return ['status' => false, 'data' => '错误:' . $e->getMessage()];
+        }
+    }
+
+
+    public function getComments($postId) {
+
+    }
+
+    public function replyToComment($commentId) {
+
+    }
+
+    public function deleteComment($commentId) {
+
+    }
+
+
+
+}

+ 5 - 2
app/Services/SmmService.php

@@ -9,8 +9,11 @@ class SmmService
 {
     protected SmmPlatformInterface $platform;
 
-    public function __construct(string $platform)
+    protected $configData = [];
+
+    public function __construct(string $platform,array $configData = [])
     {
+        $this->configData = $configData;
         $this->platform = $this->resolvePlatform($platform);
     }
 
@@ -23,7 +26,7 @@ class SmmService
             throw new InvalidArgumentException("Platform [{$platform}] not supported");
         }
 
-        $instance = new $className();
+        $instance = new $className($this->configData);
 
         if (!$instance instanceof SmmPlatformInterface) {
             throw new \RuntimeException("Platform [{$platform}] must implement SmmPlatformInterface");

+ 1 - 1
composer.json

@@ -8,7 +8,7 @@
         "php": "^8.0.2",
         "alphasnow/aliyun-oss-laravel": "^4.7",
         "dcat-plus/laravel-admin": "^1.2",
-        "facebook/graph-sdk": "^5.1",
+        "facebook/graph-sdk": "5.7.0",
         "fguillot/json-rpc": "^1.3",
         "guzzlehttp/guzzle": "^7.2",
         "laravel/framework": "^9.19",

+ 15 - 12
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "23de1a3cb4c1fc0b34eaccbfde64ac12",
+    "content-hash": "4489099b6f5efb064bf41d3199d976b9",
     "packages": [
         {
             "name": "aliyuncs/oss-sdk-php",
@@ -1049,21 +1049,20 @@
         },
         {
             "name": "facebook/graph-sdk",
-            "version": "5.1.4",
+            "version": "5.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/facebook/php-graph-sdk.git",
-                "reference": "38fd7187a6704d3ab14ded2f3a534ac4ee6f3481"
+                "reference": "2d8250638b33d73e7a87add65f47fabf91f8ad9b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/facebook/php-graph-sdk/zipball/38fd7187a6704d3ab14ded2f3a534ac4ee6f3481",
-                "reference": "38fd7187a6704d3ab14ded2f3a534ac4ee6f3481",
+                "url": "https://api.github.com/repos/facebook/php-graph-sdk/zipball/2d8250638b33d73e7a87add65f47fabf91f8ad9b",
+                "reference": "2d8250638b33d73e7a87add65f47fabf91f8ad9b",
                 "shasum": ""
             },
             "require": {
-                "ext-mbstring": "*",
-                "php": ">=5.4.0"
+                "php": "^5.4|^7.0"
             },
             "require-dev": {
                 "guzzlehttp/guzzle": "~5.0",
@@ -1071,7 +1070,8 @@
                 "phpunit/phpunit": "~4.0"
             },
             "suggest": {
-                "guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client"
+                "guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client",
+                "paragonie/random_compat": "Provides a better CSPRNG option in PHP 5"
             },
             "type": "library",
             "extra": {
@@ -1080,6 +1080,9 @@
                 }
             },
             "autoload": {
+                "files": [
+                    "src/Facebook/polyfills.php"
+                ],
                 "psr-4": {
                     "Facebook\\": "src/Facebook/"
                 }
@@ -1091,21 +1094,21 @@
             "authors": [
                 {
                     "name": "Facebook",
-                    "homepage": "https://github.com/facebook/facebook-php-sdk-v4/contributors"
+                    "homepage": "https://github.com/facebook/php-graph-sdk/contributors"
                 }
             ],
             "description": "Facebook SDK for PHP",
-            "homepage": "https://github.com/facebook/facebook-php-sdk-v4",
+            "homepage": "https://github.com/facebook/php-graph-sdk",
             "keywords": [
                 "facebook",
                 "sdk"
             ],
             "support": {
                 "issues": "https://github.com/facebook/php-graph-sdk/issues",
-                "source": "https://github.com/facebook/php-graph-sdk/tree/5.1.4"
+                "source": "https://github.com/facebook/php-graph-sdk/tree/5.7.0"
             },
             "abandoned": true,
-            "time": "2016-05-13T17:28:30+00:00"
+            "time": "2018-12-11T22:56:31+00:00"
         },
         {
             "name": "fguillot/json-rpc",

+ 5 - 1
lang/en/global.php

@@ -107,6 +107,8 @@ return [
         'video_src'             => 'Video Src',
         'review_reply'          => 'Review Reply',
         'title_en'              => 'Title',
+        'account_id'            => 'Account ID',
+        'expires_at'            => 'Expires At',
     ],
     'labels' => [
         'list'                  => 'List',
@@ -185,6 +187,7 @@ return [
         'distributor_code' => 'Distributor Code',
         'videos' => 'Videos',
         'video_category' => 'Video Category',
+        'video' => 'Video',
         'import' => 'Import',
         'update_success' => 'Update Success',
         'upload_failed' => 'Upload Failed',
@@ -209,13 +212,14 @@ return [
         'distributor_name'      => 'Distributor Name',
         'product_audit'         => 'Product Audit',
         'add_platform_account' => 'Add Platform Account',
-        'post_title'            => 'Post Title',
+        'post_message'            => 'Post Message',
         'media_type'            => 'Media Type',
         'accountsSelect'        => 'Accounts Select',
         'send_time'              => 'Send Time',
         'send_type'             => 'Send Type',
         'timing'                => 'Timing',
         'immediate'             => 'Immediate',
+        'reauthorization'       => 'Reauthorization',
     ],
     'options' => [
         //

+ 5 - 1
lang/zh_CN/global.php

@@ -113,6 +113,8 @@ return [
         'video_src'             => '视频文件',
         'review_reply'          => '回复',
         'title_en'              => '标题',
+        'account_id'            => '账号ID',
+        'expires_at'            => '过期时间',
 
     ],
     'labels' => [
@@ -191,6 +193,7 @@ return [
         'input_format' => '输入格式',
         'videos'         => '视频',
         'video_category' => '视频分类',
+        'video'          => '视频',
         'import'         => '导入',
         'update_success'         => '更新成功',
         'upload_failed'    => '上传失败',
@@ -218,13 +221,14 @@ return [
         'audit_success'      => '审核成功',
         'product_audit'      => '产品审核',
         'add_platform_account' => '添加平台账号',
-        'post_title'            => '帖子标题',
+        'post_message'            => '帖子留言',
         'media_type'            => '媒体类型',
         'accountsSelect'        => '选择账号',
         'send_time'              => '发送时间',
         'immediate'              => '立即',
         'timing'                 => '定时',
         'send_type'             => '发送类型',
+        'reauthorization'       => '重新授权',
 
     ],
     'options' => [