工作复盘

100 阅读7分钟

工作复盘

大佬勿看,从别的博文总结抄录用来自己看的!!!!

一、埋点

目的:

  • 数据监控:运营需要Google分析来分析官网首页的用户浏览量,监控用户行为,进行数据分析;
  • 错误监控:及时的上报异常情况,可以避免线上故障的发生。

1.1 script/image/link埋点

  1. 新建一个脚本,在脚本里动态创建一个带 async 的 script 标签请求埋点sdk的安装脚本,append到html的body或者head上。
  2. 通过 Image 对象的 src 属性指向上报脚本并携带参数,就可以将我们收集的数据传给后端。
  3. 把这个脚本插到xxxxx(框架里)地方, 将标签挂载到页面上。
  4. 用户浏览页面或者浏览表单,点击提交表单时就发送相关数据。

优点: 虽然埋点域名和当前域名不是同一个,但是script/image/link标签请求没有跨域的风险。

缺点: script和link标签需要挂在到页面上,载入js资源的时候会阻塞首页渲染

补充: 使用image对象无需挂载到页面上,反复操作dom。img的加载不会阻塞html的解析,但img加载后并不渲染,它需要等待Render Tree生成完后才和Render Tree一起渲染出来。通常埋点上报会使用gif图,合法的 GIF 只需要 43 个字节。

*image的缺陷:*浏览器设置了禁止图片显示时,无法触发请求。

// 通过script 需要插到DOM上
function sendByScriptInsertDOM(src){ 
    var script = document.createElement("script"); 
    script.src = src; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(script); 
} 
// 通过image 不需要
function sendByImage(src) { 
    var img = new Image(); 
    img.src = src; 
 }

来自:教你怎么前端实现埋点上报 - 掘金 (juejin.cn)

1.2 css/script阻塞

1.2.1 浏览器解析和渲染HTML过程

概念:

  • DOM解析:将HTML的各种标签生成 DOM Tree 的过程,没有任何 CSS 样式。
  • DOM渲染:将 DOM Tree 和 CSS 结合生成 render tree,并将其呈现在浏览器页面上。

DOM 解析和 DOM 渲染由两个线程完成,两者可以并行完成。

过程:

  1. DOM 解析:将 HTML 各标签生成 DOM 树,同时将 CSS 样式解析成 CSSOM 树;
  2. DOM 渲染:将 DOM 树和 CSS 树渲染成 Render tree;
  3. 进入布局阶段,将 Render 树上的节点分配他们在页面上的坐标(这一阶段还是渲染树);
  4. 进入绘制阶段,此时渲染引擎的工作就结束了,把渲染树的节点绘制在页面上。

浏览器是如何解析和渲染HTML的🔥🔥🔥 - 掘金 (juejin.cn)

1.2.2 css/js 阻塞

CSS:

  • css 不会阻塞 DOM 解析,因为 css 解析和 DOM 解析是并行的。
  • css 会阻塞 DOM 渲染,DOM解析完成了,页面上出现了 HTML 的节点,但是还没在页面上展示出来。是因为 DOM 渲染需要 CSSOM 和 DOM 树,但是 css 样式没有请求完,阻塞了 DOM 渲染,即CSS会阻塞 render tree 的生成,进而会阻塞DOM的渲染。
  • css 会阻塞 JS 的执行,JS 脚本需要等到 css样式都加载完后,才能执行,这样 JS 才能获取到 DOM 元素最新的样式。因为这个原因,script 标签一般放在 link 标签的前面
  • body 内的外链 css,会因为浏览器的差异(有些浏览器的css或造成 DOM 解析阻塞,导致页面 DOM 解析及其样式渲染推迟;有些 CSS 的加载与 DOM 的解析就是并行的,等到 CSS 加载完成后再更新样式),造成样式闪烁。

JS:

  • JS会阻塞 DOM 解析,,因为 JS可能有操作 DOM 结构的行为,比如删除节点啥的。所以浏览器会等待 JS 执行完了再继续解析 DOM。
  • JS 会触发页面渲染,浏览器无法预知脚本的具体内容,因此在碰到 script 标签时,只好先渲染一次页面,以确保 script 脚本内能获取到 DOM 最新样式。

关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析 - 掘金 (juejin.cn)

1.2.3 defer/async

1695287680748.png

绿色为 HTML 解析,蓝色 js 下载,红色为 JS 执行。

defer

  • js 下载和 HTML 解析并行,js 执行在 HTML 解析完成后。
  • 有序

async

  • js 下载和 HTML 解析并行,js 下载完后就执行,执行的时候会阻塞 HTML 解析。
  • 无序,谁先下载完就先执行谁

二、前端分页

前端拿到请求的所有数据,对数据进行处理,操作分页组件完成。

获取全部数据,获取当前页码page,和每页展示的数据条数pageSize,通过slice函数, 获取当前页展示的数据。

slice([currentPage1]pageSizepagepageSize)slice([currentPage-1]*pageSize,page * pageSize)

// 组件
<page
    :current-page="page.page"
    :page-count="page.pageCount"
    :record-count="page.recordCount"
    @change="switchToTablePage_1"
/>
async switchToTablePage_1(page = 1) {
    this.page.page = page;
    try {
        ...
        this.searchList = res.list || [];
        this.list = this.searchList.slice(
            (page - 1) * this.page.pageSize,
            this.page.pageSize * page,
        );
        this.page.pageCount = Math.ceil(
            this.searchList.length / this.page.pageSize,
        );
        this.page.recordCount = this.searchList.length;
    } catch (err) {
        this.$dialog.show(err.message);
    }
},

三、权限控制

按钮权限

1、用户登陆后请求接口,获得这个用户的权限列表,并把权限列表放到 window 上,通过window._params获取权限;

2、在vue原型上定义一个checkPermission函数用来查看权限节点:

    Vue.prototype.checkPermission = function (key, success, failure) {
        const that = this;
        const call = function (cb) {
            if (!cb) return;
            if (typeof cb === 'function') {
                cb();
            } else if (typeof cb === 'string') {
                that.$router.push(cb);
            } else if (typeof cb === 'object') {
                that.$router.push(cb);
            }
        };
        if (that.checkPermissionBool(key)) {
            call(success);
            return true;
        }
        return false;
    };

3、在页面上调用checkPermission函数,传入节点名称

<a @click="checkPermission('permission')"></a>

路由权限

  1. 初始化挂载全部路由
  2. 在前端路由元信息里面,设置一个权限 key。
  3. 对照路由表和权限表,重建路由(递归整个路由,里面做判断,如果一级路由没有这个权限节点的话,二级的路由也要隐藏起来元信息里的condition设为hidden; 如果当前路径path为空 '' 的话,就把路径设为重定向的路径)。
  4. 在每次路由跳转时,通过路由守卫beforeEach检查权限,确保手动输入url时,能根据权限进入。

接口权限

在 rbac系统,给用户设置用户角色,以及给接口模块设置一个权限码。系统可以将用户角色和接口模块关联起来。这个接口的权限码,就放到 node 层的请求接口里。发送请求的时候,框架里面用的一个中间件就会校验 rbac 权限。

四、大文件分片下载

4.1 文件下载

服务端写content-type附件下载

前端如何通过a链接下载文件 - 掘金 (juejin.cn) 通过 a 标签下载

download(href) {
    const a = document.createElement('a');
    a.href = href;
    document.body.appendChild(a);
    a.click();
    a.remove();
}
  • 同源和跨域下,都可以使用 a 标签对超链接文件进行预览或者下载

  • 同源下,超链接文件可以通过 a 标签 download 属性值更改下载文件的名称;跨域下,超链接文件不能被更改文件名称。

前端文件下载(一) - 掘金 (juejin.cn)

blob 对象

Blob 是二进制类型的大对象。将接口返回的内容转换成 blob 对象,再通过 createObjectURL 创建对象链接,通过 a 标签下载。

downloadFileHelper(fileName, fileContent) {
    const a = document.createElement('a');
    const blog = new Blob([fileContent]);

    a.download = fileName;
    const url = URL.createObjectURL(blog);
    a.href = url;
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(url);
},

封装输出文件的工具

exports.outputFile = (ctx, name, content, type) => {
    let fileType = type;
    let fileName = name;
    if (!fileType) {
        fileType = fileName.split('.').pop();
    }
    fileName = encodeURI(fileName).replace(/,/g, '');

    const fileMimeType = mime.getType(fileType) || 'application/octet-stream';
    let disposition = `attachment; filename=${fileName}; filename*=UTF-8''${fileName}`;

    ctx.set('Content-Type', fileMimeType);
    ctx.set('Content-Disposition', disposition);
    if (Buffer.isBuffer(content)) {
        ctx.body = content;
    } else {
        ctx.body = Buffer.from(content, 'base64');
    }
    ctx.type = `.${fileType}`;
};

在接入层controller中接口返回结果的时候调用

const res = await api.downloadFile(ctx, data);

前端用a标签调用,a.href 设置为对应的路由链接。

4.2 大文件分片下载

  • 后端将文件分块上传到服务器
  • 先请求一个接口,获得文件的总大小、分块总数、分片大小。
  • 调用下载接口获取各个分片。

发起请求获取分块数据:向服务器发起请求获取文件分块数据。需要注意的是,每个分块请求需要带上 Range 头部,指定要请求的数据范围。服务器需要支持 Range 头部,返回对应范围的数据。在请求分块数据时,可以通过 Promise.all() 或 async/await 等方式实现并发请求,以提高下载速度

// 定义一个数组用于存储分块数据 
const chunkData = []; 
// 发起请求获取每个分块数据 
const requests = chunks.map((chunk, index) => { 
    return new Promise((resolve, reject) => { 
        const res = await api.download();
        if(res){
            chunkData[index] = res.data; 
            resolve();
        } else {
            reject();
        }
    });
});

// 等待所有请求完成后,合并分块数据 
Promise.all(requests).then(() => { 
    const blob = new Blob(chunkData, { type: file.type }); 
    const url = URL.createObjectURL(blob); 
    // 下载完整文件 
    const a = document.createElement('a'); 
    a.href = url; a.download = file.name; 
    document.body.appendChild(a); 
    a.click(); 
    document.body.removeChild(a); 
});

链接:www.zhihu.com/question/58…

4.2 文件上传

(1) 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;

(2) 向服务端发送上传请求,上传时携带完整文件的唯一标识(建议使用MD5加密值就行),服务端判断该文件是否存在,不存在则返回同意上传信息。

(3) 接收到服务端同意上传后,按照一定的策略(串行或并行)发送各个分片数据块,每个分片携带分片信息(分片总数,分片索引,分片唯一索引,分片数据等),采用并行可实现分片秒传的功能,采用串行更好实现断点续传的功能。

(4) 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。

五、响应式布局

对不同屏幕适配做处理。

页面头部添加:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no”>
  • width=device-width: 自适应手机屏幕的尺寸宽度
  • maximum-scale: 缩放比例的最大值
  • inital-scale: 缩放的初始化
  • user-scalable: 用户的可以缩放的操作

实现响应式布局的方法:

  • 媒体查询
  • 百分比
  • vw/vh
  • rem

5.1 媒体查询

@media screen (min-width: 375px) and (max-width: 600px) {}

5.2 vh/vw

相对视窗的百分比

5.3 rem

rem是相对根html的font-size属性,1rem=16px;

对话框 rds

业务背景:用户登陆系统后,如果当前用户没有开通父账户和子账户,就弹窗显示跳转链接去开通账户页面,一旦当前用户开通过账户,或账户在流程中的话,就不再显示这个弹窗。

是通过接口里两个字段来控制弹窗跳转链接展示,比如说a字段和b字段,当a字段为0且b字段也为0的时候,然后 redis 里面设置一个字段 key C也为0,然后当b字段为1的时候 redis里的c就加一。这些是在node接入层做的逻辑,然后每次请求接口,就判断redis的状态,为0的时候就把重新创建字段d=0连带这后端返回的对象一起传到前端页面作判断,此时弹窗展示。用户两个账户都开通完后,判断redis的key值为1,并将d=1值传给前端, 此时前端不会再弹窗。