zoukankan      html  css  js  c++  java
  • 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以内。如果感兴趣的小伙伴欢迎自行去进行优化,希望以后有相关需求的小伙伴可以避免踩坑。篇中有错误的地方欢迎指出,有不懂欢迎留言。

    作者:zero

    博客地址:http://www.cnblogs.com/zero-zyq/

    本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接

    个人下载了一些相关学习的PDF文件,如果需要下载请访问百度云 点击此处访问 密码:jhuy

    如果文章的内容对你有帮助,欢迎点赞~

    为方便手机端查看博客,现正在将博客迁移至微信公众号:Salesforce零基础学习,欢迎各位关注。

  • 相关阅读:
    【环境部署】centos7安装mysql-5.7.19 group-replication
    centos7远程安装oracle11g R2详细教程-解决一切问题
    docker:构建nginx+php-fpm镜像(一):构建nginx自启动镜像
    python virtualenv 安装运行saltstack
    自动化运维:flask-bootstrap + highstock整合
    自动化运维web环境搭建:Nginx+Django+uwsgi
    计算机二级-word错题总结
    SVPWM学习笔记2
    SVPWM自学笔记
    电力拖动自动控制系统_学习笔记2
  • 原文地址:https://www.cnblogs.com/zero-zyq/p/14907381.html
Copyright © 2011-2022 走看看