Beste forumleden,

Ik heb een platform waar bol verkopers via de bol retailer API: https://api.bol.com/retailer/public/Retailer-API/index.html

Een koppeling maken met mijn platform. Ik doe nu het volgende:

1. Tabel met bol accounts ( 10 stuks )
2. Via een cronjob door de bol accounts loopen en API calls maken om de orders op te halen.

Er zitten rate limits aan de API calls bijv 5 calls per minuut mag je maken.

Ik vind de code die ik nu heb niet stabiel genoeg en hoor graag hoe dit beter kan.
Zo zijn er klanten met echt veel orders waardoor die soms vastloopt lijkt het, is een cronjob met een while loop wel de beste manier, lijkt wel alsof die oneindig doorgaat maar niet van alle klanten de orders ophaalt.

Hoor het graag. Zit even niet op de pc dus heb geen voorbeeld code, update ik morgen even.

Mvg Furio
Ben benieuwd naar de code.
Ik zou een proces per account overwegen:

Je wilt toch eigenlijk niet dat een account dat 1 order per uur ontvangt, last heeft van traagheid die veroorzaakt wordt doordat je steeds voor 2 andere accounts 1000 per uur moet ophalen.
Wat jij zegt Ivo is ook precies waar ik tegen aan loop, nu moet een nieuwe klant 90 minuten wachten soms voor zijn orders zijn opgehaald.

Zie hier de cronjob code die ervoor zorgt dat de orders vanuit bol in mijn database geschoten worden:


<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include("/home/admin/domains/domain.com/public_html/includes/database.php");
include("/home/admin/domains/domain.com/public_html/includes/global-functions.php");

$database = new database();

// check of cron nog bezig is.

$q = 'SELECT cron_done FROM cron_status WHERE cron_systeem = "orders" ORDER BY id DESC LIMIT 1';
$result = $database->query($q);


while ($row = mysqli_fetch_assoc($result))
{
    
    if ($row['cron_done'] == 1)
   {
        $q = 'INSERT INTO cron_status (tijd_begonnen, cron_systeem, cron_done) VALUES (NOW(), "orders" ,0)';
        $database->query($q);
        
          
        // je kan starten, vorige cron is done.
       
    }
    else
    {
           
            // CRON IS NOG NIET KLAAR!
            die();
    }


}

// Pak alle klanten waarbij bol gekoppeld is
$q = 'SELECT * FROM bol_accounts WHERE bol_token != "" AND status = 1';
$result = $database->query($q);

  
 

while ($rows = mysqli_fetch_assoc($result))
{

 
    // check of sessie nog actief is, zo niet vraag nieuwe token op.
    getCheckAccountNewBolToken($rows['klant_id'], $rows['id']);
    
   
    $counterTimeout = 0;
    $counter = 0;
   
    $x = 1;
    // pak tot 50 pagina's mee.
    for ($x; $x <= 50; $x++) 
    {
       
      
    
    $orderLink = "https://api.bol.com/retailer/shipments?fulfilment-method=FBR&page=".$x.""; 
        

        $headers = ['Accept' => 'application/json'];

        $ch = curl_init($orderLink);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET' );
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
           'Content-Type: application/vnd.retailer.v7+json',
           'Authorization: Bearer ' . getAccountBolToken($rows['id'], $rows['klant_id']),
           'Accept: application/vnd.retailer.v7+json',
           ));


        // execute!
        $response = curl_exec($ch);
        $err = curl_error($ch);

        // close the connection, release resources used
        curl_close($ch);

        $json = json_decode($response,true);
        
        // check of rate limit is bereikt, 429 = rate limit bereikt.
        
       if ($json['status'] == "429")
     {
        
        // limiet bereikt.
       
        $counterTimeout++;
        sleep(60); // 1 minuut rust dan kunnen we weer 7x loopen.
        
     }
       
        if ($response == "{}" || $response == "{ }" || $response == "")
        {
            
            
            // geen orders meer.
            $paginageenOrders = $x;
            $x = 51;
 
             
        }

        
        else
        {

    foreach($json as $key=>$val)
    {
        
        
        if(is_array($val))
        { 
            
            
            
            foreach($val as $key=>$val)
            {
                 
                 
                 if(is_array($val))
                 {
                    $sShipmentId = $val['shipmentId'];
                    $shipmentOrderId = $val['order']['orderId'];
                    $aantalOpgehaaldeorders++;
                    
                    // check of order al is geimporteerd, zo niet importeer.
                    $qt = 'SELECT shipment_id FROM bol_orders WHERE shipment_id = "'.$sShipmentId.'" AND klant_id = "'.$rows['klant_id'].'" AND bol_account_id = "'.$rows['id'].'" ';
                    $resultt = $database->query($qt);
                    
                    if (mysqli_num_rows($resultt) == 0)
                    {
                    
                        // voer gegevens in want order bestaat nog niet.
                        $q = 'INSERT INTO bol_orders (klant_id, bol_account_id,shipment_id,shipment_order_id) VALUES ("'.$rows['klant_id'].'", "'.$rows['id'].'", "'.$sShipmentId.'", "'.$shipmentOrderId.'")';
                        $database->query($q);
                        
   
                         
                    }
                    
                    
                    

                    $counter++;
                } 
   
            
            }
            
            
            
        }
        else
        { 
            
        }
    }
    }
    
    // zendingen zijn opgehaald.
   
    }
    
           
            
           


}



// update cron dat die klaar is.

$q = 'UPDATE cron_status SET cron_done = 1, tijd_klaar = NOW(), opgehaalde_orders = "'.$OpgehaaldeOrdersCronTotal.'" WHERE id = "'.getCronLatestId().'"';
$database->query($q);





?>


Wat Ivo zegt. Gewoon een cronjob per klant runnen.

Los van het probleem.
Een goede tip: Pas je cronjob commando aan dat je eerst naar de directorie van het script navigereert en dan met && je script met relatief pad aanroept. Dan hoef je geen hardcoded pad te gebruiken.

- Ariën - op 07/12/2023 09:38:32

Wat Ivo zegt. Gewoon een cronjob per klant runnen.

Los van het probleem.
Een goede tip: Pas je cronjob commando aan dat je eerst naar de directorie van het script navigereert en dan met && je script met relatief pad aanroept. Dan hoef je geen hardcoded pad te gebruiken.



Bedankt voor de tip! Hoe moet ik een cronjob per klant voor mij zien? Ik kan toch niet handmatig voor iedere nieuwe klant een cronjob aanmaken in directadmin?

Ja, maar volgens mij heeft DA er een mooie API voor. Je zou een vast script kunnen gebruiken met een parameter per klant.

Ik ga dit eens uitzoeken hoe dat zat.
Dat zou helemaal top zijn en anders misschien via PHP, shell_exec? las ik al wat over:

<?php
  // sort - Sorts the string in memory alphabetically.
  // uniq - Removes any duplicate cron jobs from the string in memory.
  // crontab - Writes the string in memory to the crontab file
	shell_exec("(crontab -l ; echo '* * * * * python3 /usr/local/jobs/newjob.py') | sort | uniq | crontab");
?>


Alleen de parameter (klant_id) moet ik nog mee kunnen geven en de volgende vragen die omhoog komen:

1. Kan je bij 1000 klanten wel 1000 cronjobs aanmaken welke continue runnen?
2. Wat als een klant zijn account opzegt, moet ik dan de cronjob stopzetten?
3. De Bol retailer API werkt hetzelfde bij het opaheln van producten, reviews, terugbetalingen etc, zal ik daar ook voor iedere klant een cronjob aanmaken?

Echt super bedankt voor het meedenken!!!!
Als je 1.000 klanten hebt, dan zal je vast een contract met Bol.com kunnen regelen dat je minder beperkingen zal hebben. Maar dergelijke langdurige processen kan je dan best op een andere (virtuele) server of in een aparte (docker) container runnen. Maar zover zal het nog niet zijn, denk ik. ;-)

Je zou deze cronjob kunnen draaien:
0 8 * * * cd /pad/naar/je/script && php script.php klant1


En voor klant2 kan je kiezen dat je die een uurtje later doet, afhankelijk hoe lang een cronjob duurt. Je wilt niet tegen de limieten van de Bol.com API aanlopen. Dus dat moet je even goed verspreid uitvoeren.

Bij het uitvoeren van een cronjob (gaat via de CLI) kan je controleren of $argv[1] (die bestaat dan gewoon) de klantnaam als argument bevat. Dan kan je kijken of die bestaat in je database waar je klanten in staan. Bestaat de klant niet (meer) dan kan je de cronjob via de DirectAdmin API verwijderen, of een techneut op de IT-afdeling een seintje geven om de cronjob te verwijderen.

Ik zou alle processen die moeten plaatsvinden bij de Retailer API in één klant cronjob uitvoeren. of zijn er taken die licht zijn, eventueel vaker worden uitgevoerd en gebundeld kunnen worden? Dan kan dat in een aparte globale cronjob.

Documenteer wel goed welke processen wanneer moeten plaatsvinden, en log dit ook allemaal!
Je kunt bovenstaand script ook als volgt aanpassen:

je zet nu een lock in de database om te controleren of je job mag runnen.

Ik zou gaan voor een lock op de bol_accounts tabel:

Je cron start je gewoon elke minuut. (of 5 of 10, wat jij leuk vindt).

Die kijkt naar de tabel bol_accounts en select daaruit 1.
Deze ene krijgt een lock en een tijdstip van starten daarbij.

Daarmee doe je je ding.

1 minuut later start het script weer en uit de tabel bol_accounts select je een die geen lock heeft en het langst geleden gestart is geweest.
Die krijgt ook een lock.

Eventueel doe je nog een extra check om te kijken of er niet al 5 jobs draaien.


Beide opties zien er veelbelovend uit, ik ga ze beiden vandaag proberen te testen en zal een terugkoppeling geven welke het geworden is en of dit de oplossing voor mijn probleem is geweest.

To be continued

Reageren