前端sdk - 埋点

news2024/12/25 12:34:06

目录

  • 前端sdk 之小满np
    • 安装
    • 01 搭建环境
      • 01-项目目录
      • 01-2 依赖包
      • 01-3 rollup.config.js
      • 01-4 tsconfig.json 28行
      • 01-5 package.json
      • 01-6 src / core / index.ts
      • 01-7打包效果
    • 02 初始化 Tracher
      • 02-1 core / index.ts
      • 02-2 types/ index.ts
    • 03 重写history事件 监听history | hash 路由等跳转操作事件
      • 03-1 src / utils / pv.ts
      • 03-2 src / core / index.ts
      • 03-3 npm run build 之后 创建 index.html
      • 03-4 效果
    • 04 添加用户标识
      • 04-1 src / code /index.ts
    • 05 上报 navigator.sendBeacon
      • 05-1 src / core / index.ts
      • 05-2 写一个接口 测试埋点
        • 05-2-2 安装依赖
        • 05-2-2 接口 express / index.js
        • 05-2-3 启动后端服务
        • 05-2-4 打开index.html
        • 05-2-4 测试埋点
    • 06 dom 上报
      • 06-1 src / core / index.ts
      • 06-2 npm run build 之后 index.html 修改与添加事件
      • 06-3 效果
    • 07 js报错上报 error 事件 promise报错 unhandledrejection
      • 07-1 src / core / index.ts
      • 07-2 index.html

前端sdk 之小满np

安装

  • npm init -y
  • tsc --init

01 搭建环境

01-项目目录

在这里插入图片描述

  • 介绍
    • src / types 定义类型
    • src / core 核心代码
    • src / utils / pv 工具函数
      • PV:页面访问量,即PageView,用户每次对网站的访问均被记录
  • 技术架构
    • 埋点就是 数据采集-数据处理-数据分析和挖掘,如用户停留时间,用户哪个按钮点的多,等
    • 技术架构使用ts + rollup

01-2 依赖包

npm install rollup -D
npm install rollup-plugin-dts -D
npm install rollup-plugin-typescript2 -D
npm install typescript -D

01-3 rollup.config.js

import ts from "rollup-plugin-typescript2";
import path from "path"
import dts from "rollup-plugin-dts";
export default [
  {
    //入口文件
    input: "./src/core/index.ts",
    output: [
      //打包esModule
      {
        file: path.resolve(__dirname, "./dist/index.esm.js"),
        format: "es",
      },
      //打包common js
      {
        file: path.resolve(__dirname, "./dist/index.cjs.js"),
        format: "cjs",
      },
      //打包 AMD CMD UMD
      {
        file: path.resolve(__dirname, "./dist/index.js"),
        format: "umd",
        name: "tracker",
      },
      // {
      //   input: "./src/core/index.ts",
      //   file: path.resolve(__dirname, "./dist/index.js"),
      //   format: "umd",
      //   name: "tracker",
      // },
    ],
    //配置ts
    plugins: [ts()],
  },
  {
    //打包声明文件
    input: "./src/core/index.ts",
    output: {
      file: path.resolve(__dirname, "./dist/index.d.ts"),
      format: "es",
    },
    plugins: [dts()],
  },
];

01-4 tsconfig.json 28行

    /* Modules */
    "module": "ESNext",  

01-5 package.json

  • 配置脚本、还有module、browser、keywords、files等
{
  "name": "vue-sdk",
  "version": "1.0.5",
  "description": "",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "browser":"dist/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rollup -c"
  },
  "keywords": ["前端","埋点","tracker"],
  "author": "",
  "files": ["dist"],
  "license": "ISC",
  "devDependencies": {
    "rollup": "^2.76.0",
    "rollup-plugin-dts": "^4.2.2",
    "rollup-plugin-typescript2": "^0.32.1",
    "typescript": "^4.7.4"
  }
}

01-6 src / core / index.ts

export const a = 1000;
console.log("a", a);

// "type":"module",

01-7打包效果

  • cjs
    在这里插入图片描述

02 初始化 Tracher

02-1 core / index.ts

import { DefaultOptons, TrackerConfig, Options } from "../types/index";
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
  }
  private initDef(): DefaultOptons {
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
}

02-2 types/ index.ts

/**
 * @requestUrl 接口地址
 * @historyTracker history上报
 * @hashTracker hash上报
 * @domTracker 携带Tracker-key 点击事件上报
 * @sdkVersionsdk版本
 * @extra透传字段
 * @jsError js 和 promise 报错异常上报
 */
export interface DefaultOptons {
  uuid: string | undefined,
  requestUrl: string | undefined,
  historyTracker: boolean,
  hashTracker: boolean,
  domTracker: boolean,
  sdkVersion: string | number,
  extra: Record<string, any> | undefined,
  jsError:boolean
}

//必传参数 requestUrl
export interface Options extends Partial<DefaultOptons> {
  requestUrl: string,
}

//版本
export enum TrackerConfig {
  version = '1.0.0'
}
//上报必传参数
export type reportTrackerData = {
  [key: string]: any,
  event: string,
  targetKey: string
}

03 重写history事件 监听history | hash 路由等跳转操作事件

  • PV:页面访问量,即PageView,用户每次对网站的访问均被记录
  • 主要监听了 history 和 hash
    • history API go back forward pushState replaceState
      • history 无法通过 popstate 监听 pushState replaceState 只能重写其函数 在utils/pv
    • hash 使用hashchange 监听

03-1 src / utils / pv.ts

  • 重写history事件,以便通过popstate 监听 pushState replaceState
export const createHistoryEvnent = <T extends keyof History>(type: T): () => any => {
  const origin = history[type];
  return function (this: any) {
      const res = origin.apply(this, arguments)
      var e = new Event(type)
      window.dispatchEvent(e)
      return res;
  }
}

03-2 src / core / index.ts

  • 引入 createHistoryEvnent 并且初始化之
import { DefaultOptons, TrackerConfig, Options } from "../types/index";
import { createHistoryEvnent } from "../utils/pv"
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker()
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history['pushState'] = createHistoryEvnent('pushState')
    window.history['replaceState'] = createHistoryEvnent('replaceState')
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  private captureEvents <T>(mouseEventList:string[],targetKey:string,data?:T) {
   mouseEventList.forEach(event=>{
    window.addEventListener(event,()=>{
      console.log('监听到了');
      
    })
   }) 
  }
  private initTracker(){
    // 若是用户 开启 historyTracker 循环监听用户的事件 
    if ( this.data.historyTracker ) {
      // 'history-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(['pushState','replaceState','popstate'],'history-pv')
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(['hashChange'],'hash-pv')
    } 
  }
}

03-3 npm run build 之后 创建 index.html

  • 引入打包后的文件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <script>
      new tracker({
        historyTracker: true,
      });
    </script>
  </body>
</html>

03-4 效果

  • history.pushState('aaa','','/a'
    在这里插入图片描述

04 添加用户标识

  • UV(独立访客):即Unique Visitor,访问您网站的一台电脑客户端为一个访客
    • 用户唯一表示 可以在登录之后通过接口返回的id 进行设置值 提供了setUserId

04-1 src / code /index.ts

import { DefaultOptons, TrackerConfig, Options } from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        console.log("监听到了");
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      // 'history-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(
        ["pushState", "replaceState", "popstate"],
        "history-pv"
      );
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
  }
}

05 上报 navigator.sendBeacon

  • 为什么要使用这个去上报
    • 这个上报的机制 跟 XMLHttrequest 对比 navigator.sendBeacon 即使页面关闭了 也会完成请求 而XMLHTTPRequest 不一定

05-1 src / core / index.ts

  • reportTracker
  • sendTracker
import {
  DefaultOptons,
  TrackerConfig,
  Options,
  reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        // console.log("监听到了");
        this.reportTracker({
          event,
          targetKey,
          data,
        });
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      // 'history-pv' - 此参数需要与后台进行协商定义
      // this.captureEvents(
      //   ["pushState", "replaceState", "popstate"],
      //   "history-pv"
      // );
      this.captureEvents(["pushState"], "history-pv");
      this.captureEvents(["replaceState"], "history-pv");
      this.captureEvents(["popstate"], "history-pv");
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
  }
  // 手动上报
  public sendTracker<T extends reportTrackerData>(data: T) {
    this.reportTracker(data);
  }

  // 上报用户操作 - 监听到的时候 调用
  private reportTracker<T>(data: T) {
    // 拿到上报的data数据
    const params = Object.assign(this.data, data, {
      time: new Date().getTime(),
    });
    // 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
    let headers = {
      type: "application/x-www-form-urlencoded",
    };
    let blob = new Blob([JSON.stringify(params)], headers);
    navigator.sendBeacon(this.data.requestUrl, blob);
  }
}

05-2 写一个接口 测试埋点

  • npm run build 前端先打包后,进行测试

05-2-2 安装依赖

  • 创建后端的express文件目录,安装以下依赖
    • npm i express -S
    • npm i cros -s

05-2-2 接口 express / index.js

const express = require("express")
const cors = require("cors")

const app = express()

app.use(cors())

app.use(express.urlencoded({extended:false}))

app.post("/tracker",(req,res)=>{
  console.log('req',req.body);
  res.send(200)
})

app.listen(9000,()=>{
  console.log('监听服务端口 9000');
})

05-2-3 启动后端服务

  • node index.js

05-2-4 打开index.html

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <script>
      new tracker({
        requestUrl:"http://localhost:9000/tracker",
        historyTracker: true,
      });
    </script>
  </body>
</html>

05-2-4 测试埋点

  • history.pushState('aaa','','/a')
  • 效果
    在这里插入图片描述

在这里插入图片描述

06 dom 上报

  • 主要是给需要监听的元素添加一个属性 用来区分是否需要监听 target-key

06-1 src / core / index.ts

import {
  DefaultOptons,
  TrackerConfig,
  Options,
  reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";

// dom节点操作 上报-01
const MouseEventList: string[] = [
  "click",
  "dblclick",
  "contextmenu",
  "mousedown",
  "mouseup",
  "mouseenter",
  "mouseout",
  "mouseover",
];

export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        // console.log("监听到了");
        this.reportTracker({
          event,
          targetKey,
          data,
        });
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      this.captureEvents(["pushState"], "history-pv");
      this.captureEvents(["replaceState"], "history-pv");
      this.captureEvents(["popstate"], "history-pv");
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
    // dom 节点上报 -03
    if (this.data.domTracker) {
      console.log('11');
      this.targetKeyReport();
    }
    // if (this.data.jsError) {
    //   this.jsError();
    // }
  }
  // 手动上报
  public sendTracker<T extends reportTrackerData>(data: T) {
    this.reportTracker(data);
  }

  // 上报用户操作 - 监听到的时候 调用
  private reportTracker<T>(data: T) {
    // 拿到上报的data数据
    const params = Object.assign(this.data, data, {
      time: new Date().getTime(),
    });
    // 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
    let headers = {
      type: "application/x-www-form-urlencoded",
    };
    let blob = new Blob([JSON.stringify(params)], headers);
    navigator.sendBeacon(this.data.requestUrl, blob);
  }
  // dom 节点上报 -02
  private targetKeyReport() {
    MouseEventList.forEach((event) => {
      window.addEventListener(event, (e) => {
        const target = e.target as HTMLElement;
        const targetKey = target.getAttribute("target-key");
        // 如果有上报属性,则需要上报
        if (targetKey) {
          this.reportTracker({
            event,
            targetKey,
          });
        }
      });
    });
  }
}

06-2 npm run build 之后 index.html 修改与添加事件

  • domTracker:true
  • 添加上报
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <button target-key="btn">添加上报</button>
    <button>无添加</button>
    <script>
      new tracker({
        requestUrl: "http://localhost:9000/tracker",
        historyTracker: true,
        domTracker:true
      });
    </script>
  </body>
</html>

06-3 效果

在这里插入图片描述

07 js报错上报 error 事件 promise报错 unhandledrejection

07-1 src / core / index.ts

import {
  DefaultOptons,
  TrackerConfig,
  Options,
  reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";

// dom节点操作 上报-01
const MouseEventList: string[] = [
  "click",
  "dblclick",
  "contextmenu",
  "mousedown",
  "mouseup",
  "mouseenter",
  "mouseout",
  "mouseover",
];

export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        // console.log("监听到了");
        this.reportTracker({
          event,
          targetKey,
          data,
        });
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      this.captureEvents(["pushState"], "history-pv");
      this.captureEvents(["replaceState"], "history-pv");
      this.captureEvents(["popstate"], "history-pv");
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
    // dom 节点上报 -03
    if (this.data.domTracker) {
      this.targetKeyReport();
    }
    // js错误上报 - 1
    if (this.data.jsError) {
      this.jsError();
    }
  }
  // 手动上报
  public sendTracker<T extends reportTrackerData>(data: T) {
    this.reportTracker(data);
  }

  // 上报用户操作 - 监听到的时候 调用
  private reportTracker<T>(data: T) {
    // 拿到上报的data数据
    const params = Object.assign(this.data, data, {
      time: new Date().getTime(),
    });
    // 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
    let headers = {
      type: "application/x-www-form-urlencoded",
    };
    let blob = new Blob([JSON.stringify(params)], headers);
    navigator.sendBeacon(this.data.requestUrl, blob);
  }
  // dom 节点上报 -02
  private targetKeyReport() {
    MouseEventList.forEach((event) => {
      window.addEventListener(event, (e) => {
        const target = e.target as HTMLElement;
        const targetKey = target.getAttribute("target-key");
        // 如果有上报属性,则需要上报
        if (targetKey) {
          this.reportTracker({
            event,
            targetKey,
          });
        }
      });
    });
  }

  // js错误上报 - 1
  private jsError() {
    this.errorEvent();
    this.promiseReject();
  }
  //捕获js报错
  private errorEvent() {
    window.addEventListener("error", (event) => {
      this.reportTracker({
        event: "error",
        targetKey: "message",
        message: event.message,
      });
    });
  }
  //捕获promise 错误
  private promiseReject() {
    window.addEventListener("unhandledrejection", (event) => {
      event.promise.catch((error) => {
        this.reportTracker({
          event: "promise",
          targetKey: "reject",
          // targetKey: "message",
          message: error,
        });
      });
    });
  }
}

07-2 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <button target-key="btn">添加上报</button>
    <button>无添加</button>
    <script>
      new tracker({
        requestUrl: "http://localhost:9000/tracker",
        historyTracker: true,
        domTracker:true,
        jsError:true
      });
    </script>
  </body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/194712.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Spring Cloud Alibaba】(一)微服务介绍 及 Nacos注册中心实战

文章目录前言I、微服务与Spring CloudII、Nacos 注册中心III、Spring Cloud Alibaba Nacos 实战1、新建父工程2、新建demo-a 服务3、新建 demo-b 服务4、实现服务调用&#xff1a;传统方式5、实现服务调用&#xff1a;NacosRibbon方式总结最后前言 Spring Cloud Alibaba微服务…

JS 设计模式(2)-- 复习

目录 策列模式 代理模式 观察者模式 发布订阅模式 模块模式 策列模式 策略模式定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使他们可以相互替换&#xff0c;且算法的变化不会影响使用算法的用户&#xff0c;策列模式属于对象行为模式&#xff0c;它通过…

Java面试题(自用-持续更新)

本文目录如下&#xff1a;Java面试题一、基础知识JDK 和 JRE 有什么区别&#xff1f;String 属于基础的数据类型吗&#xff1f;基础类型有哪些?String str"xqz"与 String strnew String("xqz")一样吗&#xff1f;java 中操作字符串都有哪些类&#xff1f;…

王佩丰 Excel 基础24讲 | 学习笔记(全)

第一讲&#xff1a;认识Excel 1.简介 excel能做什么&#xff1f; 数据存储 → 数据处理 → 数据分析 → 数据呈现 excel界面 补充&#xff1a;Excel数据分析步骤 ①提出问题&#xff1a;明确自己需要通过数据分析解决什么问题 ②理解数据&#xff1a;理解数据各个字段的含义…

【手写 Promise 源码】第十五篇 - 了解 generator 生成器

一&#xff0c;前言 上一篇&#xff0c;实现了 promisify 和应用场景介绍&#xff0c;主要涉及以下几个点&#xff1a; promisify 简介和测试&#xff1b;promisify 功能的实现&#xff1a;promisify、promisifyAll&#xff1b; 目前&#xff0c;Promise 部分已基本完成&…

FPGA实现图像任意位置显示,串口协议控制显示位置,提供工程源码和技术支持

目录1、图像任意位置显示理论基础2、设计思路和架构3、OV5640图像采集4、图像DDR3三帧缓存5、图像任意位置输出显示6、串口协议控制显示位置7、vivado工程介绍8、上板调试验证9、福利&#xff1a;工程源码获取1、图像任意位置显示理论基础 图像任意位置显示指的是在显示屏上的…

Linux新手渣渣上路史

时至2022年&#xff0c;IT行业的迅速发展大家也有目共睹&#xff0c;IT行业在社会的发展中起着举足轻重的作用。其中一角Linux系统&#xff0c;从诞生到开源&#xff0c;再到现在受大众的欢迎&#xff0c;是一个很好的例子。Linux和windows类似&#xff0c;是一个操作系统&…

java 微服务高级之分布式事务 Seata框架 CAP定理 BASE理论 XA模式 AT模式 TCC模式 SAGA模式

分布式事务问题 1.1.本地事务 1.2.分布式事务 一旦有一个失败了&#xff0c;其他两个不知情失败的情况&#xff0c;还是执行并成功 在分布式系统下&#xff0c;一个业务跨越多个服务或数据源&#xff0c;每个服务都是一个分支事务&#xff0c;要保证所有分支事务最终状态一致…

【JavaEE】线程安全的集合类

引言 在Java标准库中&#xff0c;大部分集合类都是线程不安全的。Vector(比ArrayList多了同步化机制就变得线程安全了)&#xff1b;Stack(继承Vector)&#xff1b;Hashtable(只比Hashmap多了线程安全)&#xff1b;以Concurrent开头的集合类&#xff1a;ConcurrentHashMap、Con…

Echarts 用图形纹理来填充颜色(color - pattern)

第006个点击查看专栏目录在上一篇文章中已经讲过 ECharts线性渐变色示例演示&#xff08;2种渐变方式&#xff09;&#xff0c;这个示例的颜色使用纹理来做填充&#xff0c;纹理填充&#xff1a; pattern color:{ //纹理填充 image: patternImg, repeat: ‘repeat’ } 示例效果…

禾川HCQ ModBUS+485主从站调试

硬件&#xff0c;485转usb&#xff0c;如果主站是plc&#xff0c;不需要这个线&#xff0c;我现在主站是电脑&#xff0c;调试用。 HCQ0 禾川控制器。 软件 modbus tools 调试软件&#xff0c;自行下载吧&#xff0c;社区传不上去。 硬件连接时注意交叉连接&#xff0c;HCQ0 A端…

MATLAB 逻辑数组

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

Java⽇志框架学习笔记

目录 1.⽇志概述 1.1 ⽇志是⽤来做什么的&#xff1f; 1.2 为什么要⽤到⽇志框架&#xff1f; 1.3 现有的⽇志框架有哪些&#xff1f; 1.4 ⽇志⻔⾯技术 2.logback 2.1 logback介绍 2.1.1 logback 模块 2.1.2 logback 组件 2.1.3 logback 配置 2.1.4 logback.xml 配…

2023网络爬虫 -- 获取动态数据

一、网站的正常界面1、网址https://movie.douban.com/typerank?type_name%E5%8A%A8%E4%BD%9C%E7%89%87&type5&interval_id100:90&action2、正常的页面二、爬取数据1、源代码import requests头{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64…

首屏加载速度慢怎么解决?

一、什么是首屏加载 首屏时间&#xff08;First Contentful Paint&#xff09;&#xff0c;指的是浏览器从响应用户输入网址地址&#xff0c;到首屏内容渲染完成的时间&#xff0c;此时整个网页不一定要全部渲染完成&#xff0c;但需要展示当前视窗需要的内容 首屏加载可以说是…

分享156个ASP源码,总有一款适合您

ASP源码 分享156个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 156个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1Mc-zWjUyk9Lx8TXv5cvZTg?pwds2qi 提取码&#x…

Office 365用户登录审核

黑客访问端点设备&#xff0c;以窃取公司特定数据、员工个人数据或任何其他可能对他们有任何用处的有价值的信息。 为了帮助您防止这种攻击&#xff0c;我们编写了一个参数列表&#xff0c;可以帮助您识别异常日志&#xff0c;这通常是攻击的第一个标志。异常登录活动是安全漏洞…

Vue使用ElementUI的确认框进行删除操作(包含前后端代码)

前言 今天做自己项目的时候&#xff0c;有一个删除的业务&#xff0c;正好遇到了确认框&#xff0c;在此纪念一下。 这里我是使用的ElementUI的确认框&#xff01; 首先ElementUI的确认框是这么说明的&#xff1a; 从场景上说&#xff0c;MessageBox 的作用是美化系统自带的 …

Java程序员跳槽,三面全过,面试官:你这样的,我们招不起

程序员小李在沿海城市工作了8年&#xff0c;那里涨幅飞快的房价限制了程序员小李在一线城市安家的想法&#xff0c;再加上突然发生的疫情暴露了远在他乡工作的不便&#xff0c;在种种因素下&#xff0c;程序员小李决定回家工作。 既然已经下定决心告别一线城市回家乡发展&…

数据分析面试-sql练习

SQL汇总1. SQL执行顺序2. 开窗函数3. 经典SQL题3.0 数据准备3.1 ☆ 查询‘01’课程比‘02’课程成绩高的学生3.2 查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩3.3 查询在SC表存在成绩的学生信息3.4 查询所有同学的学生编号、学生姓名、选课总数、所有课程的总…