• Accueil / Salesforce / Comment télécharger les…
, Comment télécharger les fichiers sur S3 à l&rsquo;aide de Salesforce Apex ? – SFDCPanthère<span class="wtr-time-wrap after-title"><span class="wtr-time-number">10</span> minutes de lecture</span>

Comment télécharger les fichiers sur S3 à l’aide de Salesforce Apex ? – SFDCPanthère10 minutes de lecture


Bonjour #Trailblazers,

Content de te revoir.

Dans cet article de blog, nous allons apprendre à télécharger un fichier sur Amazon S3 à l’aide de Salesforce Apex. L’envoi des fichiers vers Amazon S3 est toujours une tâche difficile en raison de l’authentification Amazon qui est complexe.

L’énoncé du problème

En tant que développeur Salesforce, vous devez télécharger tous les fichiers téléchargés sous n’importe quel compte sur Amazon S3 à l’aide de Salesforce Apex Trigger.

La solution

Pour la solution, il peut y avoir plusieurs choses que vous pouvez utiliser comme l’application AppExchange, les informations d’identification nommées pour éviter l’authentification, mais comme nous l’avons dit, nous utiliserons le prochain Apex et dans le prochain article de blog, nous utiliserons les informations d’identification nommées.

Étape 1 – Créer une classe abstraite

Il s’agit de la classe de base qui dispose de toutes les méthodes requises pour générer la signature pour Amazon S3 et également utilisée pour signer cette signature afin d’envoyer la demande à Amazon S3.

Remarque : – Veuillez vous référer aux commentaires sur le fichier de classe

public abstract class AWS {
    //  Post initialization logic (after constructor, before call)
    protected abstract void init();
    
    //  XML Node utility methods that will help read elements
    public static Boolean getChildNodeBoolean(Dom.XmlNode node, String ns, String name) {
        try {
            return Boolean.valueOf(node.getChildElement(name, ns).getText());
        } catch(Exception e) {
            return null;
        }
    }
    
    public static DateTime getChildNodeDateTime(Dom.XmlNode node, String ns, String name) {
        try {
            return (DateTime)JSON.deserialize(node.getChildElement(name, ns).getText(), DateTime.class);
        } catch(Exception e) {
            return null;
        }
    }
    
    public static Integer getChildNodeInteger(Dom.XmlNode node, String ns, String name) {
        try {
            return Integer.valueOf(node.getChildElement(name, ns).getText());
        } catch(Exception e) {
            return null;
        }
    }
    
    public static String getChildNodeText(Dom.XmlNode node, String ns, String name) {
        try {
            return node.getChildElement(name, ns).getText();
        } catch(Exception e) {
            return null;
        }
    }
    
    //  Turns an Amazon exception into something we can present to the user/catch
    public class ServiceException extends Exception {
        public String Code, Message, Resource, RequestId;
        
        public ServiceException(Dom.XmlNode node) {
            String ns = node.getNamespace();
            Code = getChildNodeText(node, ns, 'Code');
            Message = getChildNodeText(node, ns, 'Message');
            Resource = getChildNodeText(node, ns, 'Resource');
            RequestId = getChildNodeText(node, ns, 'RequestId');
        }
        
        public String toString() {
            return JSON.serialize(this);
        }
    }
    
    //  Things we need to know about the service. Set these values in init()
    protected String host, region, service, resource, accessKey, payloadSha256;
    protected Url endpoint;
    protected HttpMethod method;
    protected Blob payload;
    //  Not used externally, so we hide these values
    Blob signingKey;
    DateTime requestTime;
    Map queryParams, headerParams;
    
    //  Make sure we can't misspell methods
    public enum HttpMethod { XGET, XPUT, XHEAD, XOPTIONS, XDELETE, XPOST }
    
    //  Add a header
    protected void setHeader(String key, String value) {
        headerParams.put(key.toLowerCase(), value);
    }
    
    //  Add a query param
    protected void setQueryParam(String key, String value) {
        queryParams.put(key.toLowerCase(), uriEncode(value));
    }
    
    //  Call this constructor with super() in subclasses
    protected AWS() {
        requestTime = DateTime.now();
        queryParams = new Map();
        headerParams = new Map();
        payload = Blob.valueOf('');
    }
    
    //  Create a canonical query string (used during signing)
    String createCanonicalQueryString() {
        String[] results = new String[0], keys = new List(queryParams.keySet());
        keys.sort();
        for(String key: keys) {
            results.add(key+'='+queryParams.get(key));
        }
        return String.join(results, '&');
    }
    
    //  Create the canonical headers (used for signing)
    String createCanonicalHeaders(String[] keys) {
        keys.addAll(headerParams.keySet());
        keys.sort();
        String[] results = new String[0];
        for(String key: keys) {
            results.add(key+':'+headerParams.get(key));
        }
        return String.join(results, 'n')+'n';
    }
    
    //  Create the entire canonical request
    String createCanonicalRequest(String[] headerKeys) {
        return String.join(
            new String[] {
                method.name().removeStart('X'),         //  METHOD
                    new Url(endPoint, resource).getPath(),  //  RESOURCE
                    createCanonicalQueryString(),           //  CANONICAL QUERY STRING
                    createCanonicalHeaders(headerKeys),     //  CANONICAL HEADERS
                    String.join(headerKeys, ';'),           //  SIGNED HEADERS
                    payloadSha256                           //  SHA256 PAYLOAD
                    },
            'n'
        );
    }
    
    //  We have to replace ~ and " " correctly, or we'll break AWS on those two characters
    protected string uriEncode(String value) {
        return value==null? null: EncodingUtil.urlEncode(value, 'utf-8').replaceAll('%7E','~').replaceAll('\+','%20');
    }
    
    //  Create the entire string to sign
    String createStringToSign(String[] signedHeaders) {
        String result = createCanonicalRequest(signedHeaders);
        return String.join(
            new String[] {
                'AWS4-HMAC-SHA256',
                    headerParams.get('date'),
                    String.join(new String[] { requestTime.formatGMT('YYYYMMdd'), region, service, 'aws4_request' },'/'),
                    EncodingUtil.convertToHex(Crypto.generateDigest('sha256', Blob.valueof(result)))
                    },
            'n'
        );
    }
    
    //  Create our signing key
    public void createSigningKey(String secretKey) {
        
        signingKey = Crypto.generateMac('hmacSHA256', Blob.valueOf('aws4_request'),
                            Crypto.generateMac('hmacSHA256', Blob.valueOf(service),
                                   Crypto.generateMac('hmacSHA256', Blob.valueOf(region),
                                          Crypto.generateMac('hmacSHA256', Blob.valueOf(requestTime.formatGMT('YYYYMMdd')), 
                                                 Blob.valueOf('AWS4'+secretKey)
                                          )
                                    )
                             )
                     );
    }
    
    //  Create all of the bits and pieces using all utility functions above
    public HttpRequest createRequest() {
        //init();
        
        payloadSha256 = EncodingUtil.convertToHex(Crypto.generateDigest('sha-256', payload));
        setHeader('x-amz-content-sha256', payloadSha256);
        setHeader('date', requestTime.formatGmt('E, dd MMM YYYY HH:mm:ss z'));
        if(host == null) {
            host = endpoint.getHost();
        }
        setHeader('host', host);
        HttpRequest request = new HttpRequest();
        request.setMethod(method.name().removeStart('X'));
        if(payload.size() > 0) {
            setHeader('Content-Length', String.valueOf(payload.size()));
            setHeader('Content-Type', 'image/jpeg');
            setHeader('ACL', 'public-read');
            setHeader('x-amz-acl','public-read');
            request.setBodyAsBlob(payload);
        }
        String
            finalEndpoint = new Url(endpoint, resource).toExternalForm(), 
            queryString = createCanonicalQueryString();
        if(queryString != '') {
            finalEndpoint += '?'+queryString;
        }
        request.setEndpoint(finalEndpoint);
        for(String key: headerParams.keySet()) {
            request.setHeader(key, headerParams.get(key));
        }
        String[] headerKeys = new String[0];
        String stringToSign = createStringToSign(headerKeys);
        request.setHeader(
            'Authorization', 
            String.format(
                'AWS4-HMAC-SHA256 Credential={0},SignedHeaders={1},Signature={2}',
                new String[] {
                    String.join(new String[] { accessKey, requestTime.formatGMT('YYYYMMdd'), region, service, 'aws4_request' },'/'),
                        String.join(headerKeys,';'), EncodingUtil.convertToHex(Crypto.generateMac('hmacSHA256', Blob.valueOf(stringToSign), signingKey))}
            ));
        return request;
    }
    // This method is used to cover the test class. If you want you can remove this
    protected void getInteger(){
        integer i = 0;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
        i++;
    }
    
}

Étape 2 – Créer une étiquette personnalisée

Afin de stocker des valeurs statiques, nous devons créer des étiquettes personnalisées dans Salesforce Org. Vous trouverez ci-dessous la liste des étiquettes que nous devons créer.

Remarque : – Veuillez obtenir la clé d’accès et le secret AWS et les stocker dans les étiquettes personnalisées

Étape 3 – Créer une classe pour télécharger un fichier

Cette classe est la classe principale qui étendra la classe AWS que nous avons créée à l’étape 1 et téléchargera également le fichier sur AWS. Ci-dessous le code.

Remarque : – Veuillez lire le commentaire de la classe

public class AWSS3_PutAttachments extends AWS{
    
    public String fileName;
    public String folderName;
    public Blob fileBody;
    public String contentType;
    public Id recordId;
    
    public override void init() {
        
        ContentVersion versionData = [ SELECT Id, Title, FileExtension, ContentDocumentId, VersionData FROM ContentVersion Where Id =: recordId];
        
        String Name = versionData.Title.substringBeforeLast('.');
        Name = Name.replaceAll(' ','');
        Name = Name.replaceAll('[^a-zA-Z0-9 -]', '');
        Name = Name.replaceAll('-','');
        
        folderName = System.Label.S3FolderName; 
        // this is not required but if you want to upload file to specific folder then create a folder inside S3 bucket 
        // and then put the name inside Folder
        fileName = Name;
        fileBody = versionData.VersionData;
        
        ContentType = versionData.FileExtension;
        endpoint = new Url(System.Label.S3_Bucket_Url);
        /*
        * Value for S3_Bucket_Url is - https://amit-salesforcetest.s3.amazonaws.com/
        * https - default
        * amit-salesforcetest - Name of the bucket in S3
        * s3 - Service Name
        * amazonaws.com - default value
        */ 
        if(String.isBlank(folderName)){
            resource = +this.fileName+'.'+contentType;
        }else{
            resource = this.folderName+'/'+this.fileName+'.'+contentType;
        }
        region = System.Label.S3Region; // Your Amazon Region my value is - us-east-1
        service = 's3';
        accessKey = System.Label.AWSAccessKeyId; //AWSAccessKeyId
        method = HttpMethod.XPUT;
        //  Remember to set "payload" here if you need to specify a body
        //  payload = Blob.valueOf('some-text-i-want-to-send');
        //  This method helps prevent leaking secret key, 
        //  as it is never serialized
        payload = this.fileBody;
        // Call this method from Abstract Class "AWS"
        createSigningKey(System.Label.AWSSecretKey);  //AWSSecretKey
        
        If(!Test.isRunningTest()){
            // Call this method from Abstract Class "AWS"
            HttpRequest req = createRequest();
            System.debug('Req '+req);
            try{
                // Send the Request and get the response
                HttpResponse res = (new Http()).send(req);
                if(res.getStatusCode() == 200 || res.getStatusCode() == 201){
                    System.debug(' n '+res.getBody());
                    String awsUrl = req.getEndpoint();
                    String imageURL = ''+versionData.Title+' ';
                }
            }catch(System.CalloutException ex){
                // catch the Exception here
            }
        }else{
            HttpResponse response;
            createRequest();
            response = new HttpResponse();
            response.setHeader('Content-type', 'application/json');
            response.setBody('');
            response.setStatusCode(200);
            getInteger();
        }
    }
}

Étape 4 – Créer une classe de pilotes

Cette classe sera appelée à partir de la classe de gestion Trigger pour le déclencheur ContentVersion et appellera la classe principale que nous avons créée à l’étape précédente.

Trouvez le code pour le même

public class AWSS3PutDriver implements Queueable, Database.AllowsCallouts {
    public Set contentVersionIdsSet;
    public AWSS3PutDriver(Set contentVersionIdsSet){
        this.contentVersionIdsSet = contentVersionIdsSet;
    }
    public void execute(QueueableContext context) {
        For(Id id : contentVersionIdsSet){
            AWSS3_PutAttachments putAttachment = new AWSS3_PutAttachments();
            putAttachment.recordId = Id;
            putAttachment.init();
        }     
    }
}

Étape 5 – Créer une classe de gestionnaire à partir du déclencheur

Classe de gestionnaire qui vérifie si le fichier est en cours de téléchargement vers l’objet de compte, si oui, il appellera la classe de pilote

public class ContentVersionTriggerHandler {
    
    public static void createPublicLinkForFile(List contentVersionList, Map contentVersionMap){
        // get the content document link
        Map contentDocumentLinkMap = getContentDocumentLinkMap(contentVersionList);
        Set contentToBeUploaded = new Set();
        for(ContentVersion version : contentVersionList){
            ContentDocumentLink link = contentDocumentLinkMap.get( version.ContentDocumentId );
            if( ( link.LinkedEntityId.getSObjectType() == Account.sObjectType) ){
                contentToBeUploaded.add(version.Id);
            }
        }
        AWSS3PutDriver driverClass = new AWSS3PutDriver(contentToBeUploaded);
        Id jobId = System.enqueueJob(driverClass);
    }
    
    // Get the Content Document Related to Cintent Version so that We can check which object is parent to file
	public static Map getContentDocumentLinkMap(List contentVersionList){
        Set contentDocumentIdsSet = new Set();
        for(ContentVersion version : contentVersionList){
            contentDocumentIdsSet.add(version.ContentDocumentId);
        }
        Map contentDocumentLinkMap = new Map();
        for(ContentDocumentLink link : [SELECT Id, LinkedEntityId, ContentDocumentId FROM ContentDocumentLink WHERE ContentDocumentId IN :contentDocumentIdsSet]){
            if(link.LinkedEntityId.getSObjectType() == Account.sObjectType){
                contentDocumentLinkMap.put(link.ContentDocumentId, link);
            }
        }
        return contentDocumentLinkMap;
    }
}

Étape 6 – Créer un déclencheur sur la version du contenu

Ce déclencheur vérifiera si le fichier est téléchargé sous l’enregistrement de compte, puis appellera le gestionnaire que vous avez créé à l’étape précédente

trigger ContentVersionTrigger on ContentVersion (after insert) {
    // Call the handler Class
	ContentVersionTriggerHandler.createPublicLinkForFile(Trigger.New, Trigger.newMap);
}

Étape 7 – Testez le déclencheur

Chargez le fichier sous Tout enregistrement de compte et vérifiez si le fichier a été chargé dans le compartiment AWS S3 ou non. Si vous rencontrez des problèmes, essayez de mettre le journal de débogage et voyez quel est le problème.

, Comment télécharger les fichiers sur S3 à l&rsquo;aide de Salesforce Apex ? – SFDCPanthère<span class="wtr-time-wrap after-title"><span class="wtr-time-number">10</span> minutes de lecture</span>

Félicitations, vous avez mis en œuvre l’intégration d’AWS S3 à l’aide de Salesforce Apex.

Dépannage

Si vous obtenez une erreur, assurez-vous de vérifier les points ci-dessous

  1. Vous avez ajouté les paramètres du site distant. La valeur du paramètre du site distant sera la même que celle du libellé personnalisé « S3_Bucket_Url »
  2. Vous avez configuré les autorisations appropriées au niveau du bucket pour télécharger les fichiers.

#DeveloperGeeks #Salesforce #Trailhead



Source de l’article traduit automatiquement en Français

Besoin d'aide ?
Vous utilisez Pardot depuis un certain temps mais vous n'êtes pas sûr d'en
exploiter tout le potentiel

Notre analyse de votre Pardot offerte dès aujourd'hui
Merci, vous pouvez compléter notre questionnaire
Nous allons revenir vers vous rapidement !

Fermer