专栏首页salesforce零基础学习Salesforce LWC学习(三十五) 使用 REST API实现不写Apex的批量创建/更新数据

Salesforce LWC学习(三十五) 使用 REST API实现不写Apex的批量创建/更新数据

本篇参考:

https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/resources_composite_composite.htm

https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/resources_composite_sobject_tree.htm

https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/resources_composite_sobjects_collections_update.htm

https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

salesforce零基础学习(一百零三)项目中的零碎知识点小总结(五)

https://jeremyliberman.com/2019/02/11/fetch-has-been-blocked-by-cors-policy.html

我们在学习LWC的时候,使用 wire adapter特别爽,比如 createRecord / updateRecord,按照指定的格式,在前端就可以直接将数据的创建更新等操作搞定了,lwc提供的wire adapter使用的是 User Interface API来实现。当然,人都是很贪婪的,当我们对这个功能使用起来特别爽的时候,也在疑惑为什么没有批量的创建和更新的 wire adapter,这样我们针对一些简单的数据结构,就不需要写apex class,这样也就不需要维护相关的test class,不需要考虑部署的时候漏资源等等。那么,针对批量数据的场景,是否有什么方式可以不需要apex,直接前台搞定吗?当然可以,我们可以通过调用标准的rest api接口去搞定。

ContactController.cls

public with sharing class ContactController {

    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts() {
        return [
            SELECT AccountId, Id, FirstName, LastName, Title, Phone, Email
            FROM Contact limit 10
        ];
    }

    @AuraEnabled
    public static string updateContacts(Object data) {
        List<Contact> contactsForUpdate = (List<Contact>) JSON.deserialize(
            JSON.serialize(data),
            List<Contact>.class
        );
        try {
            update contactsForUpdate;
            return 'Success: contacts updated successfully';
        }
        catch (Exception e) {
            return 'The following exception has occurred: ' + e.getMessage();
        }
    }
}

datatableUpdateExample.html

<template>
    <lightning-card title="Datatable Example" icon-name="custom:custom63">
        <div class="slds-m-around_medium">
            <template if:true={contact.data}>
                <lightning-datatable
                    key-field="Id"
                    data={contact.data}
                    columns={columns}
                    onsave={handleSave}
                    draft-values={draftValues}>
                </lightning-datatable>
            </template>
            <template if:true={contact.error}>
                <!-- handle Apex error -->
            </template>
        </div>
    </lightning-card>
</template>

datatableUpdateExample.js

import { LightningElement, wire, api } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';
import { refreshApex } from '@salesforce/apex';

import { ShowToastEvent } from 'lightning/platformShowToastEvent';

import updateContacts from '@salesforce/apex/ContactController.updateContacts';


const COLS = [
    { label: 'First Name', fieldName: 'FirstName', editable: true },
    { label: 'Last Name', fieldName: 'LastName', editable: true },
    { label: 'Title', fieldName: 'Title' },
    { label: 'Phone', fieldName: 'Phone', type: 'phone' },
    { label: 'Email', fieldName: 'Email', type: 'email' }
];
export default class DatatableUpdateExample extends LightningElement {
    columns = COLS;
    draftValues = [];

    @wire(getContacts)
    contact;

    async handleSave(event) {
        const updatedFields = event.detail.draftValues;

        await updateContacts({data: updatedFields})
        .then(result => {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Success',
                    message: 'Contact updated',
                    variant: 'success'
                })
            );

            // Display fresh data in the datatable
            refreshApex(this.contact).then(() => {
                this.draftValues = [];
            });
       }).catch(error => {
           console.log(JSON.stringify(error));
           if(error.body) {
               console.log(JSON.stringify(error.body));
           } else if(error.detail) {
               console.log(JSON.stringify(error.detail));
           }
           this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error updating or refreshing records',
                    //message: error.body.message,
                    variant: 'error'
                })
            );
        });
    }
}

结果展示:

点击以后

我们在上一篇讲述了标准的rest api,那OK,我们可以尝试不适用后台apex方式去搞定,而是在前台通过rest api去玩一下,说到做到,开弄。后台 apex增加获取session的方法

public with sharing class ContactController {

    @AuraEnabled(cacheable=true)
    public static String getSessionId() {
        return UserInfo.getSessionId();
    }

    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts() {
        return [
            SELECT AccountId, Id, FirstName, LastName, Title, Phone, Email
            FROM Contact limit 10
        ];
    }
}

前端 html / javascript也同样的改造一下

import { LightningElement, wire, api, track } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';
import { refreshApex } from '@salesforce/apex';

import { ShowToastEvent } from 'lightning/platformShowToastEvent';

import updateContacts from '@salesforce/apex/ContactController.updateContacts';
import getSessionId from '@salesforce/apex/ContactController.getSessionId';

const COLS = [
    { label: 'First Name', fieldName: 'FirstName', editable: true },
    { label: 'Last Name', fieldName: 'LastName', editable: true },
    { label: 'Title', fieldName: 'Title' },
    { label: 'Phone', fieldName: 'Phone', type: 'phone' },
    { label: 'Email', fieldName: 'Email', type: 'email' }
];
export default class DatatableUpdateExample extends LightningElement {
    columns = COLS;
    draftValues = [];
    @track isShowSpinner = false;
    @track sessionId;

    @wire(getContacts)
    contact;

    handleSave(event) {
        this.isShowSpinner = true;
        const updatedFields = event.detail.draftValues;
        updatedFields.forEach(item => {
            item.attributes = {"type" : "Contact"};
        });
        let requestBody = { "allOrNone": false, "records": updatedFields };
        console.log(JSON.stringify(updatedFields));
        getSessionId()
        .then(result => {
            this.sessionId = result;
            fetch('/services/data/v50.0/composite/sobjects/',
        {
            method: "PATCH",
            body: JSON.stringify(requestBody),
            headers: {
                    "Content-Type": "application/json",
                    "Authorization": "Bearer " + this.sessionId
                }
            }).then((response) => {
                //TODO 可以通过 status code判断是否有超时或者其他异常,如果是200,则不管更新成功失败,至少response回来
                console.log(response.status);
                return response.json(); // returning the response in the form of JSON
            })
            .then((jsonResponse) => {
                console.log('jsonResponse ===> '+JSON.stringify(jsonResponse));
                if(jsonResponse) {
                    jsonResponse.forEach(item => {
                        if(item.success) {
                            console.log(item.id + 'update success');
                        } else {
                            console.log(item.id + 'update failed');
                        }
                    })
                }
                refreshApex(this.contact).then(() => {
                    this.draftValues = [];
                });
                this.isShowSpinner = false;

            })
            .catch(error => {
                console.log('callout error ===> '+JSON.stringify(error));
                this.isShowSpinner = false;
            })
        })
        .catch(error => {
            //TODO
            console.log('callout error ===> '+JSON.stringify(error));
            this.isShowSpinner = false;
        })
        
    }
}

对应html

<template>
    <lightning-card title="Datatable Example" icon-name="custom:custom63">
        <div class="slds-m-around_medium">
            <template if:true={contact.data}>
                <lightning-datatable
                    key-field="Id"
                    data={contact.data}
                    columns={columns}
                    onsave={handleSave}
                    draft-values={draftValues}>
                </lightning-datatable>
            </template>
            <template if:true={contact.error}>
                <!-- handle Apex error -->
            </template>
        </div>
    </lightning-card>
    <template if:true={isShowSpinner}>
        <lightning-spinner alternative-text="Loading" size="medium"></lightning-spinner>
    </template>
</template>

运行展示:通过下图可以看到报错了CORS相关的错误,因为跨域进行了请求,这种情况的处理很单一也不麻烦,只需要 setup去配置相关的CORS以及CSP trust site肯定没有错

下图是配置的CSP 以及CORS

但是很遗憾的是,即使配置了这些内容,还是不可以。也征集了群里大神的各种建议意见,各种尝试扩充了 request header,发现还是不行。因为准备备考integration,所以也就暂时搁置了这个尝试。周末时间相对充裕,不太甘心的忽然想到了一个事情,不要只看 console的报错,查看一下network是否有什么有用的信息。

通过这个截图我们可以看出来,这个http 操作有三次的请求,第一次是跨域的检查,request method是option,感兴趣的可以自己查看

进行了错误的这次请求的展开,将 response内容展开,发现了问题

好家伙,尽管console报错是CORS,但是其实这个问题的rootcause是 请求返回的code是401未授权,打开 rest api 文档查看一下

破案了,后台通过 UserInfo.getSessionId获取的session信息无法用于REST API的授权,这里就会有一个疑问,因为艾总发过来了一个VF的demo,是可以通过rest去调用的,难道是vf / lex哪里有区别,或者session有区别?

然后我就做了一个vf去打印一下session信息以及通过apex在lex展示session信息,发现visualforce page通过 GETSESSIONID或者 {!$Api.Session_ID}获取的session id信息和apexclass获取的session id不一致,并且 vf 获取的是可用的。OK,找到了解决方案以后,进行demo的bug fix。

GenerateSessionId.page

<apex:page contentType="application/json">
    {!$Api.Session_ID}
</apex:page>

ContactController: 只需要修改 getSessionId方法即可

@AuraEnabled(cacheable=true)
    public static String getSessionId() {
        return Page.GenerateSessionId.getContent().toString().trim();
    }

验证:搞定

总结:篇中只展示了一下通过 REST API去批量操作数据的可行性,仅作为一个简单的demo很多没有优化,异常处理,错误处理等等。而且对数据量也有要求,200以内。如果感兴趣的小伙伴欢迎自行去进行优化,希望以后有相关需求的小伙伴可以避免踩坑。篇中有错误的地方欢迎指出,有不懂欢迎留言。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Salesforce Integration 概览(二) Remote Process Invocation—Request and Reply(远程进程调用--请求和响应)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_pra...

    用户1169343
  • Salesforce Integration 概览(五) Remote Call-In(远程操作 外部->salesforce)

    本篇博客介绍 Remote Call-In 集成模式,一言以蔽之:此种模式用于存储在Lightning Platform中的数据由远程系统创建、检索、更新或删除...

    用户1169343
  • salesforce零基础学习(九十六)Platform Event浅谈

    本篇参考:https://developer.salesforce.com/blogs/2018/07/which-streaming-event-do-i-u...

    用户1169343
  • Salesforce Integration 概览(七) Data Virtualization数据可视化

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_pra...

    用户1169343
  • Salesforce LWC学习(三十) lwc superbadge项目实现

    本篇参考:https://trailhead.salesforce.com/content/learn/superbadges/superbadge_lwc_s...

    用户1169343
  • Salesforce LWC学习(五) LDS & Wire Service 实现和后台数据交互 & meta xml配置

    之前的几节都是基于前台变量进行相关的操作和学习,我们在项目中不可避免的需要获取数据以及进行DML操作。之前的内容中也有提到wire注解,今天就详细的介绍一下对数...

    用户1169343
  • salesforce零基础学习(一百零一)如何了解你的代码得运行上下文

    本篇参考:https://developer.salesforce.com/docs/atlas.en-us.228.0.apexcode.meta/apexc...

    用户1169343
  • salesforce零基础学习(一百零三)项目中的零碎知识点小总结(五)

    本篇参考:Salesforce Admin篇(四) Security 之Two-Factor Authentication & Single Sign On

    用户1169343
  • Salesforce LWC学习(十四) Continuation进行异步callout获取数据

    https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc...

    用户1169343
  • Salesforce LWC学习(二十五) Jest Test

    https://trailhead.salesforce.com/content/learn/modules/test-lightning-web-compon...

    用户1169343
  • Salesforce LWC学习(六) @salesforce & lightning/ui*Api Reference

    上一篇中我们在demo中使用了很多的 @salesforce 以及 lightning/ui*Api的方法,但是很多没有细节的展开。其实LWC中针对这些modu...

    用户1169343
  • Salesforce学习 Lwc(九)【数据初期取得与更新】运用详解

    开发自定义画面经常遇到的场景就是增删改查,关于数据更新用到的几个方法进行一下总结,常用到的有以下几种。

    repick
  • Salesforce LWC学习(二) helloWorld程序在VSCode中的实现

    上一篇我们简单的描述了一下Salesforce DX的配置以及CLI的简单功能使用,此篇主要简单描述一下LWC如何实现helloWorld以及LWC开发时应该注...

    用户1169343
  • Salesforce Integration 概览(三) Remote Process Invocation—Fire and Forget(远程进程调用-发后即弃)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_pra...

    用户1169343
  • Salesforce LWC学习(一)Salesforce DX配置

    最开始做salesforce开发时使用eclipse,后来因为太笨重以及不太方便所以改用了sublime + haoide。sublime + haoide无...

    用户1169343
  • salesforce零基础学习(一百零五)Change Data Capture

    https://developer.salesforce.com/docs/atlas.en-us.232.0.api_streaming.meta/api_s...

    用户1169343
  • Salesforce LWC学习(四) 父子component交互 / component声明周期管理 / 事件处理

    我们在上篇介绍了 @track / @api的区别。在父子 component中,针对api类型的变量,如果声明以后就只允许在parent修改,son comp...

    用户1169343
  • salesforce零基础学习(九十七)Big Object

    https://developer.salesforce.com/docs/atlas.en-us.224.0.bigobjects.meta/bigobjec...

    用户1169343
  • Salesforce LWC学习(三) import & export / api & track

    我们使用vs code创建lwc 时,文件会默认生成包含 template作为头的html文件,包含了 import LightningElement的 js...

    用户1169343

扫码关注云+社区

领取腾讯云代金券