瀏覽代碼

广告上报调整

Tree 2 天之前
父節點
當前提交
e53389f41e

+ 72 - 10
app/Http/Controllers/Game/LoginController.php

@@ -9,19 +9,20 @@ use App\Game\QuickAccountPass;
 use App\Game\QuickAccountPassStore;
 use App\Game\RePayConfig;
 use App\Game\Services\AgentService;
-use App\Game\Services\FacebookEventService;
 use App\Game\Services\OuroGameService;
 use App\Game\Services\RouteService;
 use App\Game\WebChannelConfig;
 use App\Http\Controllers\Controller;
 use App\Http\helper\NumConfig;
 use App\IpLocation;
+use App\Jobs\AfEvent;
 use App\Models\Account\AccountPhone;
 use App\Models\AccountsInfo;
 use App\Models\GamePhoneVerityCode;
 use App\Models\SystemStatusInfo;
 use App\Models\Treasure\GameScoreInfo;
 use App\Notification\TelegramBot;
+use App\Services\ApkService;
 use App\Services\StoredProcedure;
 use App\Services\VipService;
 use App\Util;
@@ -152,7 +153,6 @@ class LoginController extends Controller
 //            ->where('PhoneNum', $Phone)
 ////            ->where('Channel', $user->Channel)
 //            ->orWhere('UserID', $user->UserID)
-
         $first = GlobalUserInfo::query()->where(function ($query) use ($Phone, $user, $config) {
             $query->where(function ($q) use ($Phone, $user) {
                 $q->where('RegionID', $user->RegionID??"")->where('Phone', $Phone);
@@ -704,6 +704,15 @@ class LoginController extends Controller
                         if (intval($request->input('pwa', 0)) == 1) {
                             if (intval($user->PwaInstalled) == 0) {
                                 $user->update(['PwaInstalled' => 1]);
+                                //s2s
+                                AfEvent::dispatch([
+                                    'UserID' => $user->UserID,
+                                    'event_name' => 'Lead',
+                                    'custom_data' => [
+                                        'content_name' => 'Install Pwa1',
+                                        'status' => 1,
+                                    ],
+                                ]);
                                 $config = RouteService::getChannelConfig($request);
                                 if($config->BONUS_PWA()>0){
                                     Redis::setex('pwa_bonus:'.$user->UserID,86400,1);
@@ -1127,6 +1136,7 @@ class LoginController extends Controller
                 'FaceID'           => $user['FaceID'],
                 'NickName'         => $user['NickName'],
                 'Registed'         =>1,
+                'PwaInstalled'     =>$request->input('pwa',0),
                 //            'InsurePass' => Hash::make($request->insurePassword),
                 // Add other fields as needed
             ]);
@@ -1152,6 +1162,10 @@ class LoginController extends Controller
             return GlobalUserInfo::getGameUserInfo("UserID", $UserID);
         }
 
+        $cookieParam = $request->input('cookie', '');
+        $fbclid = ApkService::extractFbclid($cookieParam);
+        ApkService::bindCookie($globalUserInfo, $request->input('s_k', ''), $fbclid, $cookieParam);
+
         $guser = GlobalUserInfo::toWebData($globalUserInfo,true);
 
 //        if($agentUser->Higher1ID){
@@ -1201,7 +1215,25 @@ class LoginController extends Controller
         AccountsInfo::where('UserID', $UserID)->update(['UserMedal' => $defaultGameId ]);
 
         Util::WriteLog('register_params',[$request,$guser]);
-
+        //s2s上报
+        AfEvent::dispatch([
+            'UserID' => $UserID,
+            'event_name' => 'CompleteRegistration',
+            'custom_data' => [
+                'content_name' => 'register_success',
+                'status' => 1,
+            ],
+        ]);
+        if($request->input('pwa',0)){
+            AfEvent::dispatch([
+                'UserID' => $UserID,
+                'event_name' => 'Lead',
+                'custom_data' => [
+                    'content_name' => 'Install Pwa2',
+                    'status' => 1,
+                ],
+            ]);
+        }
         return response()->json(apiReturnSuc($guser, ['reg.success', 'Registro realizado com sucesso!']));//->withCookie($this->setLoginCookie($guser['sign']));
     }
 
@@ -1216,7 +1248,6 @@ class LoginController extends Controller
             return apiReturnFail(['web.reg.fail_phone_exist', 'O número de telefone já existe, altere-o e tente se cadastrar novamente!']);
         }
 
-//        $guestUser=GlobalUserInfo::where('FPID',$FPID)->first();
         $config = RouteService::getChannelConfig($request);
 
 
@@ -1254,6 +1285,7 @@ class LoginController extends Controller
         if(strstr($ActCode,'http')){
             $ActCode=explode('http',$ActCode)[0];
         }
+        $Package = 'com.uswin.game777';
         $ReferrType = 0;
         if ($ActCode) {
             //使用邀请的Code来保持邀请被邀请的用户注册渠道一致性
@@ -1365,6 +1397,7 @@ class LoginController extends Controller
                 'FaceID'           => $user['FaceID'],
                 'NickName'         => $user['NickName'],
                 'Registed'         =>1,
+                'PwaInstalled'     =>$request->input('pwa',0),
                 //            'InsurePass' => Hash::make($request->insurePassword),
                 // Add other fields as needed
             ]);
@@ -1386,10 +1419,25 @@ class LoginController extends Controller
         $agentUser = AgentService::SetUserAgent($GlobalUID, $UserID, $ActCode);
         SetNXLock::release($redisKey);
 
+//        if ($regByGuest) {
+//            return GlobalUserInfo::getGameUserInfo("UserID", $UserID);
+//        }
+
+        $cookieParam = $request->input('cookie', '');
+        $fbclid = ApkService::extractFbclid($cookieParam);
+        ApkService::bindCookie($globalUserInfo, $request->input('s_k', ''), $fbclid, $cookieParam);
         $guser = GlobalUserInfo::toWebData($globalUserInfo,true);
 
+//        if($agentUser->Higher1ID){
+//            //获取邀请者信息
+//            $inviter=AccountsInfo::where('UserID',$agentUser->Higher1ID)->first();
+//        }
         $guser['reg'] = 1;
-
+//
+//        if($type=='guest'){
+//            $guser['account'] = $account;
+//            $guser['password'] = $password;
+//        }
 
         $defaultGameId = 931;
         $recommendGame = '/game/' . $defaultGameId;
@@ -1444,14 +1492,28 @@ class LoginController extends Controller
 
         AccountsInfo::where('UserID', $UserID)->update(['UserMedal' => $defaultGameId ]);
 
-        try {
-            FacebookEventService::trackCompleteRegistration($globalUserInfo,$config);
-        }catch (\Exception $e){
+        Util::WriteLog('register_params',[$request,$guser]);
 
+        //s2s上报
+        AfEvent::dispatch([
+            'UserID' => $UserID,
+            'event_name' => 'CompleteRegistration',
+            'custom_data' => [
+                'content_name' => 'register_success',
+                'status' => 1,
+            ],
+        ]);
+        if($request->input('pwa',0)){
+            AfEvent::dispatch([
+                'UserID' => $UserID,
+                'event_name' => 'Lead',
+                'custom_data' => [
+                    'content_name' => 'Install Pwa2',
+                    'status' => 1,
+                ],
+            ]);
         }
 
-
-        Util::WriteLog('register_params',[$request,$guser]);
         return response()->json(apiReturnSuc($guser, ['reg.success', 'Registro realizado com sucesso!']));//->withCookie($this->setLoginCookie($guser['sign']));
     }
 

+ 1 - 0
app/Http/Controllers/Game/RechargeController.php

@@ -40,6 +40,7 @@ class RechargeController
 
         $list = DB::table('agent.dbo.recharge_gear')
             ->where('in_shop', 1)
+            ->where('money','>', 10)
             ->select('id','money','favorable_price','give','recommend','gear')
             ->orderBy('money', 'asc')->where('status', 1)->get();
 

+ 255 - 37
app/Http/Controllers/Game/WebRouteController.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers\Game;
 
 
+use App\Facade\TableName;
 use App\Game\Block;
 use App\Game\Config\GameBasicConfig;
 use Carbon\Carbon;
@@ -248,7 +249,7 @@ class WebRouteController extends Controller
                 }
             }
         }
-
+        $spe_key=$request->input('s_k', 0);
         $data['conf']=[
             'hall'=>env("CONFIG_24680_HALL")??GameBasicConfig::$HallServer,
             'DOLLAR'=>env("CONFIG_24680_DOLLAR")??GameBasicConfig::$DOLLAR,
@@ -290,6 +291,8 @@ class WebRouteController extends Controller
             'withdrawChannel' => ['cashapp','paypal'],
             'freeChannel' => ['paypal'],
             'sharePop' => $sharePop,
+            's_k' => $this->quickLoad($spe_key, $user['UserID']??0, $FPID, $FF, $request->input('cookie', ''))??'',
+
         ];
 
 
@@ -345,6 +348,7 @@ class WebRouteController extends Controller
             return apiReturnFail('领取失败:' . $e->getMessage());
         }
     }
+
     public function log(Request $request)
     {
         Util::writeLog("gamelog", [
@@ -359,8 +363,8 @@ class WebRouteController extends Controller
         $user = $request->user();
 
         $FPID = $request->input("bfp", "");
-        $ff=$request->input('ff', '');
-        $url_sign = $request->input('us',RouteService::getChannel($request));
+        $ff = $request->input('ff', '');
+        $url_sign = $request->input('us', RouteService::getChannel($request));
 
         $UserID = $user ? $user->UserID : "";
         $ip = $request->ip();
@@ -370,23 +374,23 @@ class WebRouteController extends Controller
 
 
         Util::writeLog("apkload", [
-            'FPID'    => $FPID,
-            'FF'      => $ff,
+            'FPID'     => $FPID,
+            'FF'       => $ff,
             'url_sign' => $url_sign,
-            'user'    => $user,
-            'ip'      => IpLocation::getRealIp(),
-            'agent'   => $agent,
-            'req'     => $request->all()
+            'user'     => $user,
+            'ip'       => IpLocation::getRealIp(),
+            'agent'    => $agent,
+            'req'      => $request->all()
         ]);
 
-        $agent=explode('AppleWebKit',$agent)[0];
+        $agent = explode('AppleWebKit', $agent)[0];
         //截取到最后一个分号Mozilla/5.0 (Linux; Android 16; SM-S936U Build/BP2A.250605.031.A3; wv) 去掉了wv和后面
         $lastSemicolon = strrpos($agent, ';');
         if ($lastSemicolon !== false) {
             $agent = substr($agent, 0, $lastSemicolon);
         }
 
-        $cookieExist = ApkService::loadCookie($UserID,$FPID,$ff);
+        $cookieExist = ApkService::loadCookie($UserID, $FPID, $ff, $request->input('cookie', ''));
         if ($cookieExist && is_array($cookieExist)) {
             $data = [];
             $data['cookie'] = $cookieExist['Cookie'] ?? "";
@@ -396,7 +400,7 @@ class WebRouteController extends Controller
             $data['type'] = $cookieExist['Platform'] ?? "";
             $data['agent'] = $cookieExist['ClickUA'] ?? "";
             $data['origin'] = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_REFERER'] ?? '*';
-            Util::writeLog("apkload", "existUser:::".json_encode($data));
+            Util::writeLog("apkload", "existUser:::" . json_encode($data));
             return apiReturnSuc($data);
         }
         $datas = [];
@@ -406,19 +410,19 @@ class WebRouteController extends Controller
             //规则1,只有一个数据,直接归1
             if (count($datas) == 1) {
                 Redis::del($key);
-                ApkService::saveCookie($UserID, $datas[0], $FPID,$ff);
-                Util::writeLog("apkload", "onlyone:::".json_encode($datas[0]));
+                ApkService::saveCookie($UserID, $datas[0], $FPID, $ff);
+                Util::writeLog("apkload", "onlyone:::" . json_encode($datas[0]));
                 return apiReturnSuc($datas[0]);
             }
             Util::WriteLog("apkload", $datas);
 
             foreach ($datas as $k => $v) {
-                if(strstr($v['agent'],$agent)||$ff==$v['ff']){
+                if (strstr($v['agent'], $agent) || $ff == $v['ff']) {
                     array_splice($datas, $k, 1);
                     Redis::set($key, json_encode($datas));
                     Redis::expire($key, 7200);
-                    ApkService::saveCookie($UserID, $v, $FPID,$ff);
-                    Util::writeLog("apkload", "sameagent:::".json_encode($v));
+                    ApkService::saveCookie($UserID, $v, $FPID, $ff);
+                    Util::writeLog("apkload", "sameagent:::" . json_encode($v));
                     return apiReturnSuc($v);
                 }
             }
@@ -426,21 +430,118 @@ class WebRouteController extends Controller
         }
         $recents = ApkService::getRecentsNew($url_sign);
         foreach ($recents as $v) {
-            if(strstr($v['agent'],$agent)||$ff==$v['ff']){
-                ApkService::saveCookie($UserID, $v, $FPID,$ff);
-                Util::writeLog("apkload", "recent:::".json_encode($v));
+            if (strstr($v['agent'], $agent) || $ff == $v['ff']) {
+                ApkService::saveCookie($UserID, $v, $FPID, $ff);
+                Util::writeLog("apkload", "recent:::" . json_encode($v));
                 return apiReturnSuc($v);
             }
         }
         return apiReturnFail("");
 
     }
+    public function quickLoad($spe_key=null, $UserID=0, $FPID='', $FF='', $cookie='')
+    {
+        $data=null;
+        $fbclid = ApkService::extractFbclid($cookie);
+
+        if($spe_key){
+            $key = "quick_{$spe_key}";
+            if(Redis::exists($key)) {
+                $data = json_decode(Redis::get($key), true);
+            }
+        }
+        if(!$data){
+            $table = TableName::QPAccountsDB() . "AccountCookie";
+            $obj=null;
+            if($spe_key) {
+                $candidates = DB::table($table)->where('SPE_KEY', $spe_key)->orderBy('CreateTime', 'desc')->get();
+                $obj = ApkService::pickBestCandidate($candidates, $fbclid);
+            }
+            if(!$obj && $UserID){
+                $candidates = DB::table($table)->where('UserID', $UserID)->orderBy('CreateTime', 'desc')->get();
+                $obj = ApkService::pickBestCandidate($candidates, $fbclid);
+            }
+            if(!$obj && (!empty($FPID) || !empty($FF))){
+                $query = DB::table($table);
+                if (!empty($FPID) && !empty($FF)) {
+                    $query->where(function ($q) use ($FPID, $FF) {
+                        $q->where('FPID', $FPID)->orWhere('FF', $FF);
+                    });
+                } elseif (!empty($FPID)) {
+                    $query->where('FPID', $FPID);
+                } else {
+                    $query->where('FF', $FF);
+                }
+
+                $candidates = $query->orderBy('CreateTime', 'desc')->get();
+                $obj = ApkService::pickBestCandidate($candidates, $fbclid);
+            }
+            if(!$obj && !empty($fbclid)){
+                $obj = DB::table($table)
+                    ->where('Cookie', 'like', '%' . $fbclid . '%')
+                    ->orderBy('CreateTime', 'desc')
+                    ->first();
+            }
+
+            if($obj){
+                $data=['type'=>$obj->Platform,'cookie'=>$obj->Cookie,'s_k'=>$obj->SPE_KEY, 'url_sign'=>$obj->UrlSign, 'params'=>$obj->Params, 'ff'=>$obj->FF, 'localStorage'=>$obj->LocalStorage];
+            }
+        }
+
+        return $data;
+    }
+    public function quickSave(Request $request)
+    {
+        $FPID = $request->input("bfp", "");
+        $ff = $request->input('ff', '');
+        $url_sign = RouteService::getChannel($request);
+
+        $UserID =$request->user()?$request->user()->UserID:0;
+        $ip = IpLocation::getRealIp();
+        $agent = $request->userAgent();
+
+
+
+        ///gg or fb
+        $type = $request->get('type') ?? "fb";
+        $cookie = $request->get('cookie') ?? '';
+        $localStorage = $request->get('ls') ?? '';
+        $params = $request->get('params') ?? '';
+
+        $origin = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_REFERER'] ?? '*';
+        $time = time();
+
+        $spe_key = $request->input('s_k',0);
+        if(!$spe_key)$spe_key=$this->md5Base62($cookie . $localStorage . $params);
+
+        $locale = $request->get('locale') ?? '';
+
+        $data = compact('ip', 'agent', 'cookie', 'type', 'url_sign', 'time', 'params', 'locale', 'origin', 'ff', 'localStorage');
+
+        $key = "quick_{$spe_key}";
+        if (Redis::exists($key)) {
+            $cached = json_decode(Redis::get($key), true);
+            if (is_array($cached)) {
+                $data = $this->mergeQuickData($cached, $data);
+            }
+        }
+
+        ApkService::saveCookie($UserID, $data, $FPID, $ff,$spe_key);
+
+        Redis::set($key, json_encode($data));
+        Redis::expire($key, 7200);
+
+        Util::WriteLog("saveQuick", $data);
+
+        return apiReturnSuc(['s_k'=>$spe_key]);
+    }
+
     public function saveEnv(Request $request)
     {
         $user = $request->user();
         $FPID = $request->input("bfp", "");
-        $ff=$request->input('ff', '');
-        $url_sign = $request->input('us',RouteService::getChannel($request));
+        $ff = $request->input('ff', '');
+        $url_sign = $request->input('us', RouteService::getChannel($request));
 
         $UserID = $user ? $user->UserID : "";
         $ip = IpLocation::getRealIp();
@@ -450,22 +551,22 @@ class WebRouteController extends Controller
 
 
         ///gg or fb
-        $type=$request->get('type')??"fb";
-        $cookie=$request->get('cookie')??'';
-        $localStorage=$request->get('ls')??'';
-        $params=$request->get('params')??'';
+        $type = $request->get('type') ?? "fb";
+        $cookie = $request->get('cookie') ?? '';
+        $localStorage = $request->get('ls') ?? '';
+        $params = $request->get('params') ?? '';
 
-        $origin = $_SERVER['HTTP_ORIGIN'] ??$_SERVER['HTTP_REFERER']?? '*';
-        $time=time();
+        $origin = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_REFERER'] ?? '*';
+        $time = time();
 
 
-        $locale=$request->get('locale')??'';
+        $locale = $request->get('locale') ?? '';
 
-        $data=compact('ip','agent','cookie','type','url_sign','time','params','locale','origin','ff','localStorage');
+        $data = compact('ip', 'agent', 'cookie', 'type', 'url_sign', 'time', 'params', 'locale', 'origin', 'ff', 'localStorage');
 
-        $cookieExist = ApkService::loadCookie($UserID,$FPID,$ff);
-        if (!$cookieExist ) {
-            ApkService::saveCookie($UserID,$data,$FPID,$ff);
+        $cookieExist = ApkService::loadCookie($UserID, $FPID, $ff, $cookie);
+        if (!$cookieExist) {
+            ApkService::saveCookie($UserID, $data, $FPID, $ff);
 
             $key = "apktmp_{$url_sign}_$ip";
             $datas = [];
@@ -486,19 +587,136 @@ class WebRouteController extends Controller
             //压入最近记录
             ApkService::addRecentsNew($data, $url_sign);
             //写入快手
-            if($type=='kw'){
-                ApkService::sendToKwai(json_decode($cookie,true),ApkService::KWAI_EVENT['EVENT_DOWNLOAD']);
+            if ($type == 'kw') {
+                ApkService::sendToKwai(json_decode($cookie, true), ApkService::KWAI_EVENT['EVENT_DOWNLOAD']);
             }
 
-            Util::WriteLog("saveEnv",$data);
+            Util::WriteLog("saveEnv", $data);
 
 
         }
 
 
+        return apiReturnSuc(1);
+    }
+
+    private function mergeQuickData(array $cached, array $current)
+    {
+        $merged = $cached;
+        foreach ($current as $k => $v) {
+            if ($k === 'cookie' || $k === 'params') {
+                continue;
+            }
+            if ($v !== '' && $v !== null) {
+                $merged[$k] = $v;
+            }
+        }
 
+        $merged['cookie'] = $this->mergeCookieString($cached['cookie'] ?? '', $current['cookie'] ?? '');
+        $merged['params'] = $this->mergeParamsString($cached['params'] ?? '', $current['params'] ?? '');
 
-        return apiReturnSuc(1);
+        return $merged;
+    }
+
+    private function mergeParamsString($cached, $current)
+    {
+        if ($cached === '') return $current;
+        if ($current === '') return $cached;
+
+        $cachedJson = json_decode($cached, true);
+        $currentJson = json_decode($current, true);
+        if (is_array($cachedJson) && is_array($currentJson)) {
+            return json_encode(array_replace_recursive($cachedJson, $currentJson), JSON_UNESCAPED_UNICODE);
+        }
+
+        $cachedArr = [];
+        $currentArr = [];
+        parse_str($cached, $cachedArr);
+        parse_str($current, $currentArr);
+        if (!empty($cachedArr) || !empty($currentArr)) {
+            return http_build_query(array_replace($cachedArr, $currentArr));
+        }
+
+        if ($cached === $current) return $current;
+        return $cached . '&' . $current;
+    }
+
+    private function mergeCookieString($cached, $current)
+    {
+        if ($cached === '') return $current;
+        if ($current === '') return $cached;
+
+        $cachedJson = json_decode($cached, true);
+        $currentJson = json_decode($current, true);
+        if (is_array($cachedJson) && is_array($currentJson)) {
+            return json_encode(array_replace_recursive($cachedJson, $currentJson), JSON_UNESCAPED_UNICODE);
+        }
+
+        $cookies = $this->parseCookiePairs($cached);
+        $newCookies = $this->parseCookiePairs($current);
+        if (!empty($cookies) || !empty($newCookies)) {
+            $cookies = array_replace($cookies, $newCookies);
+            $cookieParts = [];
+            foreach ($cookies as $k => $v) {
+                $cookieParts[] = $k . '=' . $v;
+            }
+            return implode('; ', $cookieParts);
+        }
+
+        if ($cached === $current) return $current;
+        return $cached . '; ' . $current;
+    }
+
+    private function parseCookiePairs($cookieStr)
+    {
+        $pairs = [];
+        foreach (explode(';', $cookieStr) as $segment) {
+            $segment = trim($segment);
+            if ($segment === '') {
+                continue;
+            }
+            $pos = strpos($segment, '=');
+            if ($pos === false) {
+                continue;
+            }
+            $name = trim(substr($segment, 0, $pos));
+            $value = trim(substr($segment, $pos + 1));
+            if ($name !== '') {
+                $pairs[$name] = $value;
+            }
+        }
+        return $pairs;
+    }
+
+    /**
+     * 环境短 key:MD5 二进制取前 6 字节(48bit)再 base62,长度 <=9(10 字符以内)。
+     * PHP 对齐示例:$b = substr(md5($value, true), 0, 6); 再对 $b 做相同 base62 循环。
+     */
+    private function md5Base62($value)
+    {
+        $b = substr(md5($value, true), 0, 6);
+        $bytes = array_values(unpack('C*', $b));
+        $alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+        $result = '';
+
+        while (!empty($bytes)) {
+            $quotient = [];
+            $remainder = 0;
+
+            foreach ($bytes as $byte) {
+                $acc = ($remainder << 8) + $byte;
+                $q = intdiv($acc, 62);
+                $remainder = $acc % 62;
+                if (!empty($quotient) || $q !== 0) {
+                    $quotient[] = $q;
+                }
+            }
+
+            $result = $alphabet[$remainder] . $result;
+            $bytes = $quotient;
+        }
+
+        return $result === '' ? '0' : $result;
     }
 
     public function SaveRoutes(Request $request)

+ 356 - 9
app/Jobs/AfEvent.php

@@ -2,11 +2,16 @@
 
 namespace App\Jobs;
 
+use App\Game\GlobalUserInfo;
+use App\Game\WebChannelConfig;
+use App\Services\ApkService;
+use GuzzleHttp\Client;
 use Illuminate\Bus\Queueable;
 use Illuminate\Queue\SerializesModels;
 use Illuminate\Queue\InteractsWithQueue;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 class AfEvent implements ShouldQueue
@@ -14,6 +19,7 @@ class AfEvent implements ShouldQueue
     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
     protected $data;
+    protected $user;
 
     /**
      * Create a new job instance.
@@ -34,19 +40,360 @@ class AfEvent implements ShouldQueue
     {
         Log::channel('adjustEvent')->info('enter:'.json_encode($this->data));
         if (empty($this->data)) return;
-//        return;
-        // 接收数据
-        [$UserID, $payAmt, $AdId, $eventType] = $this->data;
+
+        [$UserID, $payAmt, $AdId, $eventType, $channel, $pfType, $params] = $this->normalizePayload($this->data);
+
+        if (!$UserID) {
+            return;
+        }
 
         if (empty($eventType)) $eventType = 2;
 
-        Log::info('接收数据' . \GuzzleHttp\json_encode($this->data));
-        // 执行AF事件
-        $dao = new \App\dao\af\AfEvent();
-        $dao->pay($AdId, $UserID, $eventType);
-        $dao->paySum($AdId, $UserID, $payAmt, $eventType);
-        $dao->payCount($AdId, $UserID, $eventType);
+        Log::channel('adjustEvent')->info('接收数据' . \GuzzleHttp\json_encode($this->data));
+
+        if ($this->isFacebookPlatform($pfType)) {
+            $this->reportFacebookEvents($channel, $UserID, $payAmt, $AdId, $params);
+            return;
+        }
+        if ($payAmt && !empty($AdId)) {
+            // 执行AF事件
+            $dao = new \App\dao\af\AfEvent();
+            $dao->pay($AdId, $UserID, $eventType);
+            $dao->paySum($AdId, $UserID, $payAmt, $eventType);
+            $dao->payCount($AdId, $UserID, $eventType);
+        }
 //        $dao->recordKwaiRecharge($UserID, $payAmt);
 //        $dao->recordGoogleRecharge($UserID, $payAmt);
     }
+
+    protected function normalizePayload($data)
+    {
+        if (isset($data['UserID'])) {
+            $user = GlobalUserInfo::getGameUserInfo('UserID', $data['UserID']);
+            $this->user = $user;
+            $params = is_array($data['params'] ?? null) ? $data['params'] : [];
+            $orderSn = $params['order_sn'] ?? '';
+            $orderInfo = $orderSn === '' ? null : DB::connection('write')->table('agent.dbo.order')
+                ->where('order_sn', $orderSn)
+                ->select('AdId', 'eventType','Channel')
+                ->first();
+
+
+
+            $channel = $data['channel']
+                ?? ($orderInfo->Channel ?? null)
+                ?? ($user->Channel ?? null)
+                ?? $this->extractChannelFromCookie($data['UserID'])
+                ?? 100;
+            $channelConfig = $channel ? WebChannelConfig::getByChannel($channel) : null;
+
+            $params['config'] = $channelConfig;
+            $params['event_name'] = $data['event_name'] ?? ($params['event_name'] ?? '');
+            $params['custom_data'] = is_array($data['custom_data'] ?? null) ? $data['custom_data'] : ($params['custom_data'] ?? []);
+
+            Log::channel('adjustEvent')->info('normalizePayload:' . json_encode([
+                    'user_id' => $data['UserID'] ?? 0,
+                    'event_name' => $params['event_name'],
+                    'order_sn' => $orderSn,
+                    'order_channel' => $orderInfo->Channel ?? null,
+                    'global_channel' => $user->Channel ?? null,
+                    'final_channel' => $channel,
+                    'platform_name' => $channelConfig->PlatformName ?? null,
+                ], JSON_UNESCAPED_UNICODE));
+
+            return [
+                $data['UserID'] ?? 0,
+                $params['golds'] ?? 0,
+                $orderInfo->AdId ?? '',
+                $orderInfo->eventType ?? 2,
+                $channel,
+                $channelConfig->PlatformName ?? '',
+                $params,
+            ];
+        }
+
+        return [
+            $data[0] ?? 0,
+            $data[1] ?? 0,
+            $data[2] ?? '',
+            $data[3] ?? 2,
+            $data[4] ?? 0,
+            $data[5] ?? '',
+            $data[6] ?? [],
+        ];
+    }
+
+    protected function isFacebookPlatform($pfType)
+    {
+        return is_string($pfType) && stripos($pfType, 'fb') !== false;
+    }
+
+    protected function reportFacebookEvents($channel, $userID, $payAmt, $adId, array $params = [])
+    {
+        try {
+            $config = $params['config'] ?? null;
+            if (!$config || !$this->isFacebookPlatform($config->PlatformName ?? '')) {
+                Log::channel('adjustEvent')->info('facebook s2s skipped: invalid config', [
+                    'channel' => $channel,
+                    'user_id' => $userID,
+                    'platform_name' => $config->PlatformName ?? null,
+                ]);
+                return;
+            }
+
+            $pixelId = trim((string)($config->PlatformID ?? ''));
+            $accessToken = trim((string)($config->PlatformToken ?? ''));
+            if ($pixelId === '' || $accessToken === '') {
+                Log::channel('adjustEvent')->info('facebook s2s skipped: missing pixel config', [
+                    'channel' => $channel,
+                    'user_id' => $userID,
+                    'pixel_id' => $pixelId,
+                ]);
+                return;
+            }
+
+            $events = $this->buildFacebookEvents($userID, $payAmt, $adId, $params);
+            if (empty($events)) {
+                return;
+            }
+
+            $url = "https://graph.facebook.com/v21.0/{$pixelId}/events";
+            $payload = [
+                'data' => $events,
+                'access_token' => $accessToken,
+            ];
+
+            Log::channel('adjustEvent')->info('facebook s2s request ' . json_encode([
+                    'channel' => $channel,
+                    'user_id' => $userID,
+                    'pixel_id' => $pixelId,
+                    'event_count' => count($events),
+                    'payload' => $payload,
+                ], JSON_UNESCAPED_UNICODE));
+
+            $client = new Client([
+                'timeout' => 10,
+                'http_errors' => false,
+            ]);
+            $response = $client->post($url, ['json' => $payload]);
+
+            Log::channel('adjustEvent')->info('facebook s2s response ' . json_encode([
+                    'channel' => $channel,
+                    'user_id' => $userID,
+                    'status' => $response->getStatusCode(),
+                    'body' => (string)$response->getBody(),
+                ], JSON_UNESCAPED_UNICODE));
+        } catch (\Throwable $e) {
+            Log::channel('adjustEvent')->info('facebook s2s failed ' . json_encode([
+                    'channel' => $channel,
+                    'user_id' => $userID,
+                    'message' => $e->getMessage(),
+                ], JSON_UNESCAPED_UNICODE));
+        }
+    }
+
+    protected function buildFacebookEvents($userID, $payAmt, $adId, array $params = [])
+    {
+        $cookieInfo = $this->getFacebookCookieInfo($userID);
+
+        if (!empty($params['event_name'])) {
+            return $this->buildSingleFacebookEvent($userID, $adId, $params, $cookieInfo);
+        }
+
+        $orderSn = $params['order_sn'] ?? '';
+        $first = !empty($params['first']);
+        $isD0 = !empty($params['isd0']);
+        $currency = env('CONFIG_24680_CURRENCY', 'BRL');
+
+        $userData = $this->buildFacebookUserData($userID, $adId, $params, $cookieInfo);
+        $customData = array_filter([
+            'value' => (float)$payAmt,
+            'currency' => $currency,
+        ], function ($value) {
+            return $value !== '' && $value !== null;
+        });
+
+        $origin = $cookieInfo['Origin'] ?? '';
+        $events = [];
+
+        if ($first) {
+            if ($isD0) {
+                $events[] = $this->makeFacebookEvent('firstpayD0', 'd0_' . $orderSn, $userData, $customData, $origin);
+                $events[] = $this->makeFacebookEvent('AddToWishlist', 'aw_' . $orderSn, $userData, $customData, $origin);
+            } else {
+                $events[] = $this->makeFacebookEvent('firstpayD1', 'd1_' . $orderSn, $userData, $customData, $origin);
+                $events[] = $this->makeFacebookEvent('AddToCart', 'ac_' . $orderSn, $userData, $customData, $origin);
+            }
+        } else if (!$isD0) {
+            $events[] = $this->makeFacebookEvent('payagain', 'pa_' . $orderSn, $userData, $customData, $origin);
+            $events[] = $this->makeFacebookEvent('InitiateCheckout', 'ic_' . $orderSn, $userData, $customData, $origin);
+        }
+
+        $events[] = $this->makeFacebookEvent('Purchase', 'pay_' . $orderSn, $userData, $customData, $origin);
+
+        return $events;
+    }
+
+    protected function buildSingleFacebookEvent($userID, $adId, array $params = [], array $cookieInfo = [])
+    {
+        $user = $this->user;
+        $eventName = trim((string)($params['event_name'] ?? ''));
+        if ($eventName === '') {
+            return [];
+        }
+
+        $eventId = $eventName . '_' . ($user->FPID ?? '');
+
+        $customData = is_array($params['custom_data'] ?? null) ? $params['custom_data'] : [];
+        $customData = array_filter($customData, function ($value) {
+            return $value !== '' && $value !== null;
+        });
+
+        return [
+            $this->makeFacebookEvent(
+                $eventName,
+                $eventId,
+                $this->buildFacebookUserData($userID, $adId, $params, $cookieInfo),
+                $customData,
+                $cookieInfo['Origin'] ?? ''
+            )
+        ];
+    }
+
+    protected function buildFacebookUserData($userID, $adId, array $params = [], array $cookieInfo = [])
+    {
+        $user = $this->user;
+        if (empty($cookieInfo)) {
+            $cookieInfo = $this->getFacebookCookieInfo($userID);
+        }
+        $cookieValues = $this->parseCookieValues($cookieInfo['Cookie'] ?? '');
+        $browserIds = $this->resolveFacebookBrowserIds($userID, $params, $cookieInfo, $cookieValues);
+
+        $externalId = $params['udid'] ?? ($user ? md5($user->UserID) : md5($userID));
+
+        $userData = [
+            'external_id' => (string)$externalId,
+            'fbp' => $browserIds['fbp'],
+            'fbc' => $browserIds['fbc'],
+            'client_ip_address' => $cookieInfo['IP'] ?? '',
+            'client_user_agent' => $cookieInfo['ClickUA'] ?? $cookieInfo['GameUA'] ?? '',
+        ];
+
+        return array_filter($userData, function ($value) {
+            return $value !== '' && $value !== null;
+        });
+    }
+
+    protected function getFacebookCookieInfo($userID)
+    {
+        $user = $this->user;
+        return ApkService::loadCookie($userID, $user->FPID ?? '', $user->FF ?? '') ?: [];
+    }
+
+    protected function parseCookieValues($cookieString)
+    {
+        $cookies = [];
+        if (!is_string($cookieString) || $cookieString === '') {
+            return $cookies;
+        }
+
+        foreach (explode(';', $cookieString) as $part) {
+            $part = trim($part);
+            if ($part === '' || strpos($part, '=') === false) {
+                continue;
+            }
+
+            [$name, $value] = explode('=', $part, 2);
+            $cookies[trim($name)] = trim($value);
+        }
+
+        return $cookies;
+    }
+
+    protected function resolveFacebookBrowserIds($userID, array $params, array $cookieInfo, array $cookieValues)
+    {
+        $fbp = trim((string)($cookieValues['_fbp'] ?? ''));
+        $fbc = trim((string)($cookieValues['_fbc'] ?? ''));
+        $cookieParamsFbclid = '';
+
+        if (!empty($cookieInfo['Params'])) {
+            $cookieParams = json_decode($cookieInfo['Params'], true);
+            if (is_array($cookieParams)) {
+                $cookieParamsFbclid = trim((string)($cookieParams['fbclid'] ?? ''));
+            }
+        }
+
+        if ($cookieParamsFbclid === '') {
+            return compact('fbp', 'fbc');
+        }
+
+        $timestamp = $this->resolveFacebookCookieTimestamp($cookieInfo);
+        $currentFbclid = ApkService::extractFbclid($fbc);
+
+        if ($fbc === '' || $currentFbclid === '' || $currentFbclid !== $cookieParamsFbclid) {
+            $fbc = 'fb.1.' . $timestamp . '.' . $cookieParamsFbclid;
+        }
+
+        if ($fbp === '') {
+            $seed = implode('|', [
+                $userID,
+                $cookieInfo['FPID'] ?? '',
+                $cookieInfo['FF'] ?? '',
+                $cookieInfo['IP'] ?? '',
+                $cookieInfo['CreateTime'] ?? '',
+            ]);
+            $fbp = 'fb.1.' . $timestamp . '.' . substr(md5($seed), 0, 16);
+        }
+
+        return compact('fbp', 'fbc');
+    }
+
+    protected function resolveFacebookCookieTimestamp(array $cookieInfo)
+    {
+        $rawTime = $cookieInfo['CreateTime'] ?? '';
+        if (!empty($rawTime)) {
+            $timestamp = strtotime((string)$rawTime);
+            if ($timestamp !== false) {
+                return (string)($timestamp * 1000);
+            }
+        }
+
+        return (string)round(microtime(true) * 1000);
+    }
+
+    protected function extractChannelFromCookie($userID)
+    {
+        $cookieInfo = $this->getFacebookCookieInfo($userID);
+        if (!$cookieInfo || empty($cookieInfo['Params'])) {
+            return null;
+        }
+
+        $params = json_decode($cookieInfo['Params'], true);
+        if (!is_array($params)) {
+            return null;
+        }
+
+        return $params['c'] ?? null;
+    }
+
+    protected function makeFacebookEvent($eventName, $eventId, array $userData, array $customData, $eventSourceUrl = '')
+    {
+        $event = [
+            'event_name' => $eventName,
+            'event_time' => time(),
+            'event_id' => $eventId,
+            'action_source' => 'website',
+            'user_data' => $userData,
+        ];
+
+        if (!empty($customData)) {
+            $event['custom_data'] = (object)$customData;
+        }
+
+        if (!empty($eventSourceUrl)) {
+            $event['event_source_url'] = $eventSourceUrl;
+        }
+
+        return $event;
+    }
 }

+ 7 - 19
app/Jobs/Order.php

@@ -9,7 +9,6 @@ use App\Models\PrivateMail;
 use App\Services\Custom;
 use App\Services\FirstPayStatService;
 use App\Services\OrderServices;
-use App\Game\Services\FacebookEventService;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
@@ -93,28 +92,13 @@ class Order implements ShouldQueue
                 'isd0' => (time()-strtotime($user->RegisterDate)<86400),
                 'first' => $isfirst?1:0,
                 'golds' => $payAmt,
-                'udid' => $user_id,
+                'udid' => md5($user_id),
                 'order_sn' => $order_sn
             ];
 
 
             //通知24680
-            OuroGameService::notifyWebHall($user_id,"","pay_finish",["Golds"=>$Score,"PayNum"=>$favorable_price,"event"=>"pay","params"=>$item]);
-
-            try {
-                FacebookEventService::trackPayEvent(
-                    $user_id,
-                    $order_sn,
-                    $payAmt,
-                    'USD',
-                    $isfirst?1:0,
-                    (time()-strtotime($user->RegisterDate)<86400),
-                    $user->Channel
-                );
-            }catch (\Exception $e){
-
-            }
-
+            OuroGameService::notifyWebHall($user_id,"","pay_finish",["Golds"=>$Score,"PayNum"=>$favorable_price*100,"event"=>"pay","params"=>$item]);
 
 //            OuroGameService::notifyWebHall($user_id,"",'call_client',["type"=>"track","event"=>"pay","params"=>$item]);
 
@@ -123,6 +107,10 @@ class Order implements ShouldQueue
                 $data = json_decode(Redis::get("user_pay_order_$user_id"), true);
             }
             array_push($data, $item);
+            AfEvent::dispatch([
+                'UserID' => $user_id,
+                'params' => $item,
+            ]);
 
             Log::info('订单记录成功user_pay_order_:' . json_encode($data));
             //记录redisevent,24小时
@@ -130,7 +118,7 @@ class Order implements ShouldQueue
             Redis::expire("user_pay_order_$user_id", 86400);
 //            }
 
-                //新的邀请系统生效了
+            //新的邀请系统生效了
 //            $callback = DB::table(TableName::agent() . 'UserCallBack')->where('UserID', $user_id)->first();
 //            if($callback){
 //                if($callback->call_state != 2){

+ 537 - 118
app/Services/ApkService.php

@@ -12,124 +12,531 @@ use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Redis;
 
+/**
+ * AccountCookie 广告归因服务。
+ *
+ * 核心职责:管理 AccountCookie 表,将用户的广告点击(Facebook / Google / Kwai)
+ * 与注册账号关联起来,用于后续的 S2S 事件上报(CAPI / Kwai / Google Offline Conversion)。
+ *
+ * 关键概念:
+ *  - AccountCookie:落地页 JS 上报的广告点击信息(_fbc、fbclid、gclid 等),存入 DB。
+ *  - FPID (BrowserFingerPrintID):浏览器指纹 ID,前端通过 FingerprintJS 生成。
+ *  - FF (FontFingerprint):字体指纹哈希,辅助设备识别。
+ *  - SPE_KEY:quickSave 时生成的短哈希,用于跨页面快速匹配。
+ *  - fbclid:Facebook 广告点击 ID,从 _fbc cookie 中提取(格式 fb.1.{ts}.{fbclid}),
+ *            同一次广告点击在不同浏览器中 fbclid 相同,是跨浏览器匹配的关键依据。
+ *  - UrlSign:渠道号(Channel),用于区分不同推广渠道。
+ *
+ * 数据流:
+ *  1. 用户点击广告 → 落地页 JS 调用 saveEnv/quickSave → saveCookie() 写入 AccountCookie(UserID=0)
+ *  2. 用户注册 → bindCookie() 将注册的 UserID 回填到匹配的 AccountCookie 记录
+ *  3. 用户登录/充值 → loadCookie() 读取 AccountCookie → AfEvent 上报 Facebook CAPI
+ *
+ * 匹配安全原则:
+ *  - "尽量不绑错" 优先于 "尽量绑上"
+ *  - 绝不覆盖已绑定其他 UserID 的记录,避免串号
+ *  - 跨渠道纠偏必须满足:唯一命中(或 fbclid 精准匹配) + 时间窗口内
+ */
 class ApkService
 {
 
-    //'ip','agent','cookie','type','url_sign','time'
-    public static function updateCookie($UserID, $arr){
-        DB::table(TableName::QPAccountsDB() . 'AccountCookie')->updateOrInsert(['UserID'=>$UserID],$arr);
+    /**
+     * 按 UserID 更新已有的 AccountCookie 记录(充值/首存等事件触发后更新 UniqueAction)。
+     * 仅用于已确定 UserID 的场景,不涉及指纹匹配,安全无串号风险。
+     */
+    public static function updateCookie($UserID, $arr)
+    {
+        DB::table(TableName::QPAccountsDB() . 'AccountCookie')->updateOrInsert(['UserID' => $UserID], $arr);
         $key = "AccountCookie_$UserID";
         Redis::set($key, json_encode($arr));
-        Redis::expire($key,36000);
+        Redis::expire($key, 36000);
     }
-    //'ip','agent','cookie','type','url_sign','time'
-    public static function saveCookie($UserID, $data,$FPID='',$FF='')
+
+    /**
+     * 保存广告点击的 Cookie 数据到 AccountCookie 表。
+     *
+     * 查重优先级:SPE_KEY > UserID > FPID > FF。
+     * 安全保护:如果通过 FPID/FF 找到的记录已绑定了其他 UserID,
+     * 不覆盖旧记录,而是插入新记录,防止串号。
+     */
+    public static function saveCookie($UserID, $data, $FPID = '', $FF = '',$SPE_KEY='')
     {
         $agent = $_SERVER['HTTP_USER_AGENT'];
-//        $key = "AccountCookie_$UserID";
-//        if(empty($UserID)&&!empty($FPID))$key="AccountCookie_$FPID";
-//        if (Redis::exists($key)) {
-//            return;
-//        }
-        $checkKey="";$checkValue="";
-        if(!empty($UserID)){
-            $checkKey='UserID';
-            $checkValue=$UserID;
-        }else if(!empty($FPID)){
-            $checkKey='FPID';
-            $checkValue=$FPID;
-        }else if(!empty($FF)){
-            $checkKey='FF';
-            $checkValue=$FF;
-        }
-
-        if(empty($checkKey))return;
-
-        $userExist=DB::table(TableName::QPAccountsDB() . "AccountCookie")->where($checkKey, $checkValue)->exists();
-
-        if (!$userExist) {
-
-//            if(DB::table(TableName::QPAccountsDB() . "AccountCookie")->where('Params', $data['params'])->exists()){
-//                $data['params']='';
-            //重复注册,下蛊吧
-//                self::ControlOldUser($UserID,$data);
-//            }
-
-            $arr = [
-                'UserID'    => $UserID,
-                'FPID'      => $FPID,
-                'FF'        => $FF,
-                'Cookie'    => $data['cookie'],
-                'Params'    => $data['params'],
-                'UrlSign'   => $data['url_sign'],
-                'Platform'  => $data['type'],
-                'GameUA'    => $agent,
-                'ClickUA'   => $data['agent'],
-                'Origin'   => $data['origin']??"",
-                'Locale'   => $data['locale']??$_SERVER['HTTP_ACCEPT_LANGUAGE'],
-                'ClickTime' => date('Y-m-d H:i:s', $data['time']),
-                'UniqueAction'   => 2,
-                'IP'=>IpLocation::getIP(),
-            ];
-            DB::table(TableName::QPAccountsDB() . 'AccountCookie')->insert($arr);
-//            Redis::set($key, json_encode($arr));
-//            Redis::expire($key,36000);
+        $table = TableName::QPAccountsDB() . 'AccountCookie';
+
+        $checkKey = "";
+        $checkValue = "";
+        if (!empty($SPE_KEY)) {
+            $checkKey = 'SPE_KEY';
+            $checkValue = $SPE_KEY;
+        } else if (!empty($UserID)) {
+            $checkKey = 'UserID';
+            $checkValue = $UserID;
+        } else if (!empty($FPID)) {
+            $checkKey = 'FPID';
+            $checkValue = $FPID;
+        } else if (!empty($FF)) {
+            $checkKey = 'FF';
+            $checkValue = $FF;
+        }
+
+        if (empty($checkKey)) return;
+
+        $existing = DB::table($table)->where($checkKey, $checkValue)->first();
+        $arr = [
+            'UserID'       => $UserID,
+            'FPID'         => $FPID,
+            'FF'           => $FF,
+            'SPE_KEY'      => $SPE_KEY,
+            'Cookie'       => $data['cookie'],
+            'Params'       => $data['params'],
+            'LocalStorage' => $data['localStorage']?? "",
+            'UrlSign'      => $data['url_sign'],
+            'Platform'     => $data['type'],
+            'GameUA'       => $agent,
+            'ClickUA'      => $data['agent'],
+            'Origin'       => $data['origin'] ?? "",
+            'Locale'       => $data['locale'] ?? $_SERVER['HTTP_ACCEPT_LANGUAGE'],
+            'ClickTime'    => date('Y-m-d H:i:s', $data['time']),
+            'UniqueAction' => 2,
+            'IP'           => IpLocation::getIP(),
+        ];
+        if (!$existing) {
 
-            if(!empty($UserID)) {
+            DB::table($table)->insert($arr);
+            if (!empty($UserID)) {
                 if (strstr($arr['Params'], 'gclid') || strstr($arr['Params'], 'fbclid')) {
                     (new AccountsSource())->notExistsInsert($UserID, "APK_AD(" . $arr['Platform'] . ")");
                 }
             }
-            //kwai手打点
 
-
-            if($arr['Platform']=='kw') {
+            if ($arr['Platform'] == 'kw') {
                 $cookie = $arr['Cookie'];
-                ApkService::sendToKwai(json_decode($cookie, true), ApkService::KWAI_EVENT['EVENT_COMPLETE_REGISTRATION']);
             }
-            if($arr['Platform']=='gg') {
+            if ($arr['Platform'] == 'gg') {
                 $cookie = $arr['Params'];
-//                UploadOfflineConversion::reg($UserID,json_decode($cookie, true));
+            }
+        }else if(!empty($data['cookie'])){
+            $existingUserID = $existing->UserID ?? 0;
+
+            if (!empty($existingUserID) && $existingUserID != 0
+                && !empty($UserID) && $UserID != $existingUserID) {
+                $arr['UserID'] = $UserID;
+                DB::table($table)->insert($arr);
+                return;
+            }
+
+            $shouldInsertNew = false;
+            if (in_array($checkKey, ['FPID', 'FF'])) {
+                $newFbclid = self::extractFbclid($data['cookie'] ?? '');
+                $existingFbclid = self::extractFbclid($existing->Cookie ?? '');
+                $newChannel = $data['url_sign'] ?? '';
+                $existingChannel = $existing->UrlSign ?? '';
+
+                if (!empty($newFbclid) && !empty($existingFbclid) && $newFbclid !== $existingFbclid) {
+                    $shouldInsertNew = true;
+                } elseif (!empty($newChannel) && !empty($existingChannel) && $newChannel !== $existingChannel) {
+                    $newParamsFbclid = '';
+                    $existingParamsFbclid = '';
+                    $newParams = json_decode($data['params'] ?? '', true);
+                    $existingParams = json_decode($existing->Params ?? '', true);
+                    if (is_array($newParams)) $newParamsFbclid = $newParams['fbclid'] ?? '';
+                    if (is_array($existingParams)) $existingParamsFbclid = $existingParams['fbclid'] ?? '';
+
+                    if (!empty($newParamsFbclid) && !empty($existingParamsFbclid) && $newParamsFbclid !== $existingParamsFbclid) {
+                        $shouldInsertNew = true;
+                    }
+                }
+            }
+
+            if ($shouldInsertNew) {
+                DB::table($table)->insert($arr);
+            } else {
+                if (!empty($existingUserID) && $existingUserID != 0) {
+                    $arr['UserID'] = $existingUserID;
+                }
+                DB::table($table)->where('ID', $existing->ID)->update($arr);
             }
         }
     }
+    /**
+     * 注册后将 UserID 回填到匹配的 AccountCookie 记录。
+     *
+     * 两阶段匹配:
+     *  1. 同渠道(sameChannel):UrlSign == Channel,匹配即绑。
+     *  2. 跨渠道纠偏(crossChannel):FBIOS → Safari/PWA 跳转导致渠道不一致时的兜底。
+     *     额外约束:唯一命中(或 fbclid 精准匹配) + 时间窗口内。
+     *     成功后把用户 Channel 纠正为 Cookie 的 UrlSign(同步 GlobalUserInfo + AccountsInfo)。
+     *
+     * 匹配器优先级(高→低):
+     *  SPE_KEY > FPID+FF+IP > FPID+FF > FPID+IP > FF+IP > FPID > FF > IP(仅跨渠道,窗口5min)
+     *
+     * 多条候选时用 fbclid 精准匹配,匹配不到则取最新。
+     *
+     * @param  object &$user     GlobalUserInfo(引用,跨渠道命中会修改 Channel)
+     * @param  string $SPE_KEY   quickSave 生成的特征哈希
+     * @param  string $fbclid    从 _fbc cookie 提取的 Facebook 点击 ID
+     * @param  string $cookie    客户端上报的完整 cookie 串(与 API 域名不同域时需 body 传参)
+     * @return object|false      更新后的 AccountCookie 记录,或 false
+     */
+    public static function bindCookie(&$user, $SPE_KEY='', $fbclid='', $cookie='')
+    {
+        // 注册成功后回填 AccountCookie 的 UserID。
+        // 这里的目标不是“尽量绑上”,而是“尽量不绑错”。
+        $UserID=$user->UserID;
+        $FPID=$user->FPID;
+        $FF=$user->FF;
+        $IP=$user->RegisterIP;
+        $Channel = $user->Channel ?? '';
+        $registerTime = !empty($user->RegisterDate) ? strtotime($user->RegisterDate) : time();
+        if (empty($UserID)) {
+            return false;
+        }
+
+        $logContext = [
+            'user_id' => $UserID,
+            'channel' => $Channel,
+            'fpid' => $FPID,
+            'ff' => $FF,
+            'ip' => $IP,
+            'spe_key' => $SPE_KEY,
+            'fbclid' => $fbclid,
+            'cookie_has_fbc' => is_string($cookie) && strpos($cookie, '_fbc') !== false,
+            'register_date' => $user->RegisterDate ?? '',
+        ];
+
+        $table = TableName::QPAccountsDB() . 'AccountCookie';
+        $updateArr = ['UserID' => $UserID];
+        if (!empty($FPID)) {
+            $updateArr['FPID'] = $FPID;
+        }
+        if (!empty($FF)) {
+            $updateArr['FF'] = $FF;
+        }
+        if (!empty($SPE_KEY)) {
+            $updateArr['SPE_KEY'] = $SPE_KEY;
+        }
+
+        $baseQuery = function () use ($table) {
+            // 只允许回填原始未绑定用户的数据,避免覆盖已经有 UserID 的老记录。
+            return DB::table($table)
+                ->where('UserID', 0);
+        };
 
-    public static function loadCookie($UserID,$FPID='',$FF='')
+        $sameChannelMatchers = [];
+        $crossChannelMatchers = [];
+
+        // 匹配强度从高到低排列:越靠前越可信。
+        // 先跑“同渠道”命中;只有完全找不到时,才尝试“跨渠道纠偏”。
+        if (!empty($SPE_KEY)) {
+            $sameChannelMatchers[] = ['label' => 'same_channel:SPE_KEY', 'query' => $baseQuery()->where('SPE_KEY', $SPE_KEY)];
+            $crossChannelMatchers[] = ['label' => 'cross_channel:SPE_KEY', 'query' => $baseQuery()->where('SPE_KEY', $SPE_KEY)];
+        }
+        if (!empty($FPID) && !empty($FF) && !empty($IP)) {
+            $sameChannelMatchers[] = ['label' => 'same_channel:FPID+FF+IP', 'query' => $baseQuery()->where('FPID', $FPID)->where('FF', $FF)->where('IP', $IP)];
+            $crossChannelMatchers[] = ['label' => 'cross_channel:FPID+FF+IP', 'query' => $baseQuery()->where('FPID', $FPID)->where('FF', $FF)->where('IP', $IP)];
+        }
+        if (!empty($FPID) && !empty($FF)) {
+            $sameChannelMatchers[] = ['label' => 'same_channel:FPID+FF', 'query' => $baseQuery()->where('FPID', $FPID)->where('FF', $FF)];
+            $crossChannelMatchers[] = ['label' => 'cross_channel:FPID+FF', 'query' => $baseQuery()->where('FPID', $FPID)->where('FF', $FF)];
+        }
+        if (!empty($FPID) && !empty($IP)) {
+            $sameChannelMatchers[] = ['label' => 'same_channel:FPID+IP', 'query' => $baseQuery()->where('FPID', $FPID)->where('IP', $IP)];
+            $crossChannelMatchers[] = ['label' => 'cross_channel:FPID+IP', 'query' => $baseQuery()->where('FPID', $FPID)->where('IP', $IP)];
+        }
+        if (!empty($FF) && !empty($IP)) {
+            $sameChannelMatchers[] = ['label' => 'same_channel:FF+IP', 'query' => $baseQuery()->where('FF', $FF)->where('IP', $IP)];
+            $crossChannelMatchers[] = ['label' => 'cross_channel:FF+IP', 'query' => $baseQuery()->where('FF', $FF)->where('IP', $IP)];
+        }
+        if (!empty($FPID)) {
+            $sameChannelMatchers[] = ['label' => 'same_channel:FPID', 'query' => $baseQuery()->where('FPID', $FPID)];
+            $crossChannelMatchers[] = ['label' => 'cross_channel:FPID', 'query' => $baseQuery()->where('FPID', $FPID)];
+        }
+        if (!empty($FF)) {
+            $sameChannelMatchers[] = ['label' => 'same_channel:FF', 'query' => $baseQuery()->where('FF', $FF)];
+            $crossChannelMatchers[] = ['label' => 'cross_channel:FF', 'query' => $baseQuery()->where('FF', $FF)];
+        }
+        if (!empty($IP)) {
+            $crossChannelMatchers[] = [
+                'label' => 'cross_channel:IP_only',
+                'query' => $baseQuery()->where('IP', $IP),
+                'time_window' => 300,
+            ];
+        }
+
+        foreach ($sameChannelMatchers as $matcher) {
+            $query = $matcher['query'];
+            if (!empty($Channel)) {
+                $query->where('UrlSign', $Channel);
+            }
+
+            $candidates = (clone $query)
+                ->orderBy('CreateTime', 'desc')
+                ->orderBy('ID', 'desc')
+                ->get();
+
+            $record = self::pickBestCandidate($candidates, $fbclid);
+            if (!$record) {
+                continue;
+            }
+
+            Util::WriteLog('bindCookie', $logContext + [
+                    'stage' => 'matched',
+                    'matcher' => $matcher['label'],
+                    'before' => (array)$record,
+                ]);
+            $rowUpdate = self::applyFbcCookieBackfillToUpdate($updateArr, $record, $cookie);
+            DB::table($table)->where('ID', $record->ID)->update($rowUpdate);
+            $updatedRecord = DB::table($table)->where('ID', $record->ID)->first();
+            Util::WriteLog('bindCookie', $logContext + [
+                    'stage' => 'updated',
+                    'matcher' => $matcher['label'],
+                    'after' => (array)$updatedRecord,
+                ]);
+            return $updatedRecord;
+        }
+
+        foreach ($crossChannelMatchers as $matcher) {
+            $query = $matcher['query'];
+            if (!empty($Channel)) {
+                $query->where('UrlSign', '<>', $Channel);
+            }
+
+            $records = (clone $query)
+                ->orderBy('CreateTime', 'desc')
+                ->orderBy('ID', 'desc')
+                ->get();
+
+            if ($records->isEmpty()) {
+                continue;
+            }
+
+            if ($records->count() > 1 && empty($fbclid)) {
+                continue;
+            }
+
+            $record = self::pickBestCandidate($records, $fbclid);
+            $cookieTime = strtotime($record->CreateTime);
+            $maxWindow = $matcher['time_window'] ?? 600;
+            if ($cookieTime === false || abs($cookieTime - $registerTime) > $maxWindow) {
+                Util::WriteLog('bindCookie', $logContext + [
+                        'stage' => 'skipped',
+                        'matcher' => $matcher['label'],
+                        'reason' => 'time_window_exceeded',
+                        'candidate' => (array)$record,
+                    ]);
+                // 纠偏只接受注册前后 10 分钟内的 Cookie,防止把历史 Cookie 误绑到新用户。
+                continue;
+            }
+
+            Util::WriteLog('bindCookie', $logContext + [
+                    'stage' => 'matched',
+                    'matcher' => $matcher['label'],
+                    'before' => (array)$record,
+                    'channel_corrected_to' => $record->UrlSign,
+                ]);
+            $rowUpdate = self::applyFbcCookieBackfillToUpdate($updateArr, $record, $cookie);
+            DB::table($table)->where('ID', $record->ID)->update($rowUpdate);
+            if (!empty($record->UrlSign) && isset($user->Channel) && $user->Channel != $record->UrlSign) {
+                $oldChannel = $user->Channel;
+                $user->Channel = $record->UrlSign;
+                try {
+                    $user->save();
+                } catch (\Exception $exception) {
+                    Log::error($exception->getMessage());
+
+                }
+                DB::table(TableName::QPAccountsDB() . 'AccountsInfo')
+                    ->where('UserID', $UserID)
+                    ->update(['Channel' => $record->UrlSign]);
+                Util::WriteLog('bindCookie', $logContext + [
+                        'stage' => 'channel_corrected',
+                        'old_channel' => $oldChannel,
+                        'new_channel' => $record->UrlSign,
+                    ]);
+            }
+            $updatedRecord = DB::table($table)->where('ID', $record->ID)->first();
+            Util::WriteLog('bindCookie', $logContext + [
+                    'stage' => 'updated',
+                    'matcher' => $matcher['label'],
+                    'after' => (array)$updatedRecord,
+                    'channel_corrected_to' => $user->Channel ?? '',
+                ]);
+            return $updatedRecord;
+        }
+
+        Util::WriteLog('bindCookie', $logContext + ['stage' => 'no_match']);
+        return false;
+
+    }
+
+    /**
+     * 加载用户的 AccountCookie 记录,用于 S2S 事件上报(CAPI / Kwai / Google)。
+     *
+     * 查找优先级:UserID 精确查 → FPID/FF 指纹查。
+     * 多条候选时用 fbclid 精准匹配,匹配不到则取最新。
+     * 通过 FPID/FF 找到记录后,仅补充空缺字段(FPID/FF/UserID),
+     * 绝不覆盖已有的 UserID,防止串号。
+     *
+     * @param  string $UserID  用户 ID
+     * @param  string $FPID    浏览器指纹 ID
+     * @param  string $FF      字体指纹哈希
+     * @param  string $cookie  _fbc cookie 值或完整 cookie 字符串,用于 fbclid 匹配
+     * @return array|null      AccountCookie 记录数组,或 null
+     */
+    public static function loadCookie($UserID='', $FPID = '', $FF = '', $cookie = '')
     {
-//        $key = "AccountCookie_$UserID";
-//        if (Redis::exists($key)) {
-//            return json_decode(Redis::get($key), true);
-//        }
-        if(!empty($UserID)){
-            $ac=DB::table(TableName::QPAccountsDB() . "AccountCookie")->where('UserID', $UserID)->first();
-            if(!$ac&&(!empty($FPID)||!empty($FF))){
-                //查找FPID和FF
-                $ac=DB::table(TableName::QPAccountsDB() . "AccountCookie")->where('FPID', $FPID)->orwhere('FF', $FF)->first();
-                if($ac){
-                    $updateArr=[
-                        'UserID'=>$UserID
-                    ];
-                    if(!empty($FPID))$updateArr['FPID']=$FPID;
-                    if(!empty($FF))$updateArr['FF']=$FF;
-                    if($ac->FPID==$FPID){
-                        DB::table(TableName::QPAccountsDB() . 'AccountCookie')->where('FPID', $FPID)->update($updateArr);
-                    }else{
-                        DB::table(TableName::QPAccountsDB() . 'AccountCookie')->where('FF', $FF)->update($updateArr);
+        $table = TableName::QPAccountsDB() . 'AccountCookie';
+        $ac = null;
+        $fbclid = self::extractFbclid($cookie);
 
-                    }
+        if (!empty($UserID)) {
+            $candidates = DB::table($table)
+                ->where('UserID', $UserID)
+                ->orderBy('CreateTime', 'desc')
+                ->get();
+            $ac = self::pickBestCandidate($candidates, $fbclid);
+        }
+
+        if (!$ac && (!empty($FPID) || !empty($FF))) {
+            $query = DB::table($table);
+            if (!empty($FPID) && !empty($FF)) {
+                $query->where(function ($q) use ($FPID, $FF) {
+                    $q->where('FPID', $FPID)->orWhere('FF', $FF);
+                });
+            } elseif (!empty($FPID)) {
+                $query->where('FPID', $FPID);
+            } else {
+                $query->where('FF', $FF);
+            }
+
+            $candidates = $query->orderBy('CreateTime', 'desc')->get();
+            $ac = self::pickBestCandidate($candidates, $fbclid);
+
+            if ($ac) {
+                $updateArr = [];
+                if (!empty($FPID) && empty($ac->FPID)) $updateArr['FPID'] = $FPID;
+                if (!empty($FF) && empty($ac->FF)) $updateArr['FF'] = $FF;
+
+                if (!empty($UserID) && (empty($ac->UserID) || $ac->UserID == 0)) {
+                    $updateArr['UserID'] = $UserID;
+                }
+
+                if (!empty($updateArr)) {
+                    DB::table($table)->where('ID', $ac->ID)->update($updateArr);
                 }
             }
         }
+
         if ($ac) {
-            $ac = json_decode(json_encode($ac), true);
-//            Redis::set($key, json_encode($ac));
-//            Redis::expire($key, 36000);
-            return $ac;
+            return (array)$ac;
         }
         return null;
+    }
+
+    /**
+     * bindCookie 成功更新行时:若请求里带有效 _fbc,而库中 Cookie 字段尚无 _fbc,则补写 Cookie。
+     *
+     * @param  array    $updateArr  原 update 数组(UserID/FPID/FF/SPE_KEY)
+     * @param  object   $record     当前命中的 AccountCookie 行
+     * @param  string   $incoming   客户端上报的 cookie 串
+     * @return array                合并后的 update 数组
+     */
+    protected static function applyFbcCookieBackfillToUpdate(array $updateArr, $record, $incomingCookie)
+    {
+        $merged = self::mergeIncomingFbcIntoAccountCookie($record->Cookie ?? '', $incomingCookie);
+        if ($merged !== null) {
+            $updateArr['Cookie'] = $merged;
+        }
+
+        return $updateArr;
+    }
+
+    /**
+     * 请求侧有有效 _fbc,且库中 Cookie 不含 _fbc 时,生成应写入的 Cookie 全文。
+     * - 库为空:采用客户端完整串(含 _fbp 等)
+     * - 库有其它键无 _fbc:前置 _fbc=...; 保留原串
+     *
+     * @return string|null 需更新时返回新串;否则 null
+     */
+    public static function mergeIncomingFbcIntoAccountCookie($storedCookie, $incomingCookie)
+    {
+        $incomingCookie = is_string($incomingCookie) ? trim($incomingCookie) : '';
+        if ($incomingCookie === '') {
+            return null;
+        }
+        if (self::extractFbclid($incomingCookie) === '') {
+            return null;
+        }
+        $storedCookie = trim((string)$storedCookie);
+        if ($storedCookie !== '' && preg_match('/_fbc\s*=/i', $storedCookie)) {
+            return null;
+        }
+
+        if (preg_match('/_fbc\s*=\s*([^;]+)/', $incomingCookie, $m)) {
+            $fbcPair = '_fbc=' . trim($m[1]);
+        } elseif (preg_match('/\bfb\.\d+\.\d+\.[^;\s]+/', $incomingCookie, $m)) {
+            $fbcPair = '_fbc=' . $m[0];
+        } else {
+            return null;
+        }
+
+        if ($storedCookie === '') {
+            return $incomingCookie;
+        }
+
+        return $fbcPair . '; ' . $storedCookie;
+    }
+
+    /**
+     * 从 _fbc cookie 中提取 fbclid。
+     *
+     * _fbc 格式:fb.{version}.{timestamp}.{fbclid}
+     * 例:fb.1.1774133029549.IwZXh0bgNhZW0BMABh...
+     *
+     * 支持三种输入:
+     *  - 完整 cookie 字符串(_fbc=fb.1.xxx;_fbp=...)→ 提取 _fbc 值再取第4段
+     *  - 纯 _fbc 值(fb.1.xxx.yyy)→ 直接取第4段
+     *  - 纯 fbclid 字符串 → 原样返回
+     *
+     * @param  string $cookieStr  cookie 字符串
+     * @return string             fbclid,或空串
+     */
+    public static function extractFbclid($cookieStr)
+    {
+        if (empty($cookieStr)) return '';
+        $fbc = '';
+        if (preg_match('/_fbc=([^;]+)/', $cookieStr, $m)) {
+            $fbc = $m[1];
+        } elseif (str_starts_with($cookieStr, 'fb.')) {
+            $fbc = $cookieStr;
+        }
+        if (empty($fbc)) return '';
+        $parts = explode('.', $fbc, 4);
+        if (count($parts) < 4 || empty($parts[3])) return '';
+        return $parts[3];
+    }
+
+    /**
+     * 从多条候选记录中,用 fbclid 匹配最精准的一条,匹配不到则取最新。
+     * $candidates 必须已按 CreateTime desc 排序。
+     */
+    public static function pickBestCandidate($candidates, $fbclid = '')
+    {
+        if ($candidates->isEmpty()) return null;
+        if ($candidates->count() === 1) return $candidates->first();
 
+        if (!empty($fbclid)) {
+            $match = $candidates->first(function ($row) use ($fbclid) {
+                return !empty($row->Cookie) && str_contains($row->Cookie, $fbclid);
+            });
+            if ($match) return $match;
+        }
+
+        return $candidates->first();
     }
 
+    /** 将一条广告点击数据压入该渠道的最近记录队列(最多保留 50 条)。 */
     public static function addRecentsNew($data, $url_sign)
     {
         $recents = self::getRecentsNew($url_sign);
@@ -139,6 +546,7 @@ class ApkService
         }
         Redis::set("recent_apks_$url_sign", json_encode($recents));
     }
+
     public static function setRecentsNew($recents, $url_sign)
     {
         Redis::set("recent_apks_$url_sign", json_encode($recents));
@@ -153,21 +561,24 @@ class ApkService
         if (!isset($recents) || empty($recents)) $recents = [];
         return $recents;
     }
-    public const KWAI_EVENT=[
-        'EVENT_DOWNLOAD'=>'EVENT_DOWNLOAD',//download
-        'EVENT_COMPLETE_REGISTRATION'=>'EVENT_COMPLETE_REGISTRATION',//reg
-        'EVENT_PURCHASE'=>'EVENT_PURCHASE',//purchase
-        'EVENT_FIRST_DEPOSIT'=>'EVENT_FIRST_DEPOSIT',//d0
-        'EVENT_ADD_TO_CART'=>'EVENT_ADD_TO_CART',//
-        'EVENT_CONTENT_VIEW'=>'EVENT_CONTENT_VIEW',//
+
+    public const KWAI_EVENT = [
+        'EVENT_DOWNLOAD'              => 'EVENT_DOWNLOAD',//download
+        'EVENT_COMPLETE_REGISTRATION' => 'EVENT_COMPLETE_REGISTRATION',//reg
+        'EVENT_PURCHASE'              => 'EVENT_PURCHASE',//purchase
+        'EVENT_FIRST_DEPOSIT'         => 'EVENT_FIRST_DEPOSIT',//d0
+        'EVENT_ADD_TO_CART'           => 'EVENT_ADD_TO_CART',//
+        'EVENT_CONTENT_VIEW'          => 'EVENT_CONTENT_VIEW',//
     ];
-    public static function kwaiEvent($userid,$event,$event_values=[])
+
+    /** 快手 S2S 事件上报:根据 UserID 加载 Cookie,如果是快手渠道则发送事件。 */
+    public static function kwaiEvent($userid, $event, $event_values = [])
     {
 
 //        $cookies=['Cookie'    => $data['cookie'],
 //                  'UrlSign'   => $data['url_sign'],
 //                  'Platform'  => $data['type']]
-        $user=self::loadCookie($userid);
+        $user = self::loadCookie($userid);
         /*
          * {
               CampaignID: '987654321',
@@ -177,19 +588,22 @@ class ApkService
               pixel_id: '12345678987654321'
             }
          */
-        if(isset($user)&&$user['Platform']=='kw') {
+        if (isset($user) && $user['Platform'] == 'kw') {
             $params = json_decode($user['Cookie'], true);
             self::sendToKwai($params, $event, $event_values);
         }
 
     }
-    public static function sendToKwai($params,$event,$event_values=[]){
 
-        if((isset($params['pixel_id'])&&$params['pixel_id']=='545692252418097195')||(isset($params['kwai_api_id'])&&$params['kwai_api_id']=='545692252418097195')){
-            $access_token='1Wvai0OBGGQlO9eVZIm+wnv1tq5hL9IudwMRdntfms4=';
+    /** 向快手广告 API 发送转化事件(生产模式,trackFlag=false)。 */
+    public static function sendToKwai($params, $event, $event_values = [])
+    {
+
+        if ((isset($params['pixel_id']) && $params['pixel_id'] == '545692252418097195') || (isset($params['kwai_api_id']) && $params['kwai_api_id'] == '545692252418097195')) {
+            $access_token = '1Wvai0OBGGQlO9eVZIm+wnv1tq5hL9IudwMRdntfms4=';
         }
-        if((isset($params['pixel_id'])&&$params['pixel_id']=='545692410115539004')||(isset($params['kwai_api_id'])&&$params['kwai_api_id']=='545692410115539004')){
-            $access_token='cwYDbfB+9lWmVaSBWNxhFUVtL764Or8AzxGVclhG44g=';
+        if ((isset($params['pixel_id']) && $params['pixel_id'] == '545692410115539004') || (isset($params['kwai_api_id']) && $params['kwai_api_id'] == '545692410115539004')) {
+            $access_token = 'cwYDbfB+9lWmVaSBWNxhFUVtL764Or8AzxGVclhG44g=';
         }
         try {
             $url = 'http://www.adsnebula.com/log/common/api';
@@ -199,27 +613,30 @@ class ApkService
                 "event_name"      => $event,
                 "is_attributed"   => 1,
                 "mmpcode"         => "PL",
-                "pixelId"         => $params['pixel_id']??$params['kwai_api_id'],
+                "pixelId"         => $params['pixel_id'] ?? $params['kwai_api_id'],
                 "pixelSdkVersion" => "9.9.9",
                 "properties"      => json_encode($event_values),
                 "testFlag"        => false,
                 "trackFlag"       => false
             ];
             $httpCurl = new HttpCurl();
-            $result = $httpCurl->curlPost($url, $data, 'json',true);
+            $result = $httpCurl->curlPost($url, $data, 'json', true);
             Util::WriteLog("kwai", $result);
-        }catch (\Exception $e){
-            Util::WriteLog("kwai_error", compact("params","event","event_values"));
-            Util::WriteLog("kwai_error", $e->getMessage().$e->getTraceAsString());
+        } catch (\Exception $e) {
+            Util::WriteLog("kwai_error", compact("params", "event", "event_values"));
+            Util::WriteLog("kwai_error", $e->getMessage() . $e->getTraceAsString());
 
         }
     }
-    public static function sendToKwaiTest($params,$event,$event_values=[]){
-        if((isset($params['pixel_id'])&&$params['pixel_id']=='545692252418097195')||(isset($params['kwai_api_id'])&&$params['kwai_api_id']=='545692252418097195')){
-            $access_token='1Wvai0OBGGQlO9eVZIm+wnv1tq5hL9IudwMRdntfms4=';
+
+    /** 向快手广告 API 发送转化事件(测试模式,trackFlag=true)。 */
+    public static function sendToKwaiTest($params, $event, $event_values = [])
+    {
+        if ((isset($params['pixel_id']) && $params['pixel_id'] == '545692252418097195') || (isset($params['kwai_api_id']) && $params['kwai_api_id'] == '545692252418097195')) {
+            $access_token = '1Wvai0OBGGQlO9eVZIm+wnv1tq5hL9IudwMRdntfms4=';
         }
-        if((isset($params['pixel_id'])&&$params['pixel_id']=='545692410115539004')||(isset($params['kwai_api_id'])&&$params['kwai_api_id']=='545692410115539004')){
-            $access_token='cwYDbfB+9lWmVaSBWNxhFUVtL764Or8AzxGVclhG44g=';
+        if ((isset($params['pixel_id']) && $params['pixel_id'] == '545692410115539004') || (isset($params['kwai_api_id']) && $params['kwai_api_id'] == '545692410115539004')) {
+            $access_token = 'cwYDbfB+9lWmVaSBWNxhFUVtL764Or8AzxGVclhG44g=';
         }
         try {
             $url = 'http://www.adsnebula.com/log/common/api';
@@ -229,29 +646,31 @@ class ApkService
                 "event_name"      => $event,
                 "is_attributed"   => 1,
                 "mmpcode"         => "PL",
-                "pixelId"         => $params['pixel_id']??$params['kwai_api_id'],
+                "pixelId"         => $params['pixel_id'] ?? $params['kwai_api_id'],
                 "pixelSdkVersion" => "9.9.9",
                 "properties"      => json_encode($event_values),
                 "testFlag"        => false,
                 "trackFlag"       => true
             ];
             $httpCurl = new HttpCurl();
-            $result = $httpCurl->curlPost($url, $data, 'json',true);
+            $result = $httpCurl->curlPost($url, $data, 'json', true);
             Util::WriteLog("kwai", $data);
             Util::WriteLog("kwai", $result);
-        }catch (\Exception $e){
-            Util::WriteLog("kwai_error", compact("params","event","event_values"));
-            Util::WriteLog("kwai_error", $e->getMessage().$e->getTraceAsString());
+        } catch (\Exception $e) {
+            Util::WriteLog("kwai_error", compact("params", "event", "event_values"));
+            Util::WriteLog("kwai_error", $e->getMessage() . $e->getTraceAsString());
 
         }
     }
-    public static function ControlOldUser($UserID,$Params='')
+
+    /** 对老用户设置游戏控分参数(首次设置后不再重复写入)。 */
+    public static function ControlOldUser($UserID, $Params = '')
     {
         $openGames = config('games.openKGame');
         $query = DB::table('QPTreasureDB.dbo.UserScoreControl')->where('UserID', $UserID)->first();
         if (!$query) {
             $build_sql = DB::connection('write')->table('QPTreasureDB.dbo.UserScoreControl');
-            Util::WriteLog('control_old_user', compact("UserID","Params"));
+            Util::WriteLog('control_old_user', compact("UserID", "Params"));
             $data = [
                 'ControlScore'   => (int)(-40 * NumConfig::NUM_VALUE),
                 'EffectiveScore' => 0,