从0到1实现一个前端监控系统(附源码)

news2024/11/17 7:53:00

目录

一、从0开始

二、上报数据方法

三、上报时机

四、性能数据收集上报

收集上报FP

收集上报FCP

收集上报LCP

收集上报DOMContentLoaded

收集上报onload数据

收集上报资源加载时间

收集上报接口请求时间

五、错误数据收集上报

收集上报资源加载错误

收集上报js错误

收集上报promise错误

六、行为数据收集上报

收集上报pv、uv

页面上报停留时长

用户点击上报

七、改造完善四维监控类

总结

可参考文章

参考资料


在我已有的职业生涯中,前端确实大多数时候是在裸奔的,这是这篇文章被我写出来的理由。而且,现在是一个数据时代,没有数据很多时候就没有反馈没有下一步,也就没有进步。

一个完整的前端监控平台包括三个部分:数据采集与上报、数据整理和存储、数据展示。本文只写数据采集与上报部分。

图片

数据采集 (1).png

图片

数据上报.png

一、从0开始

名字很重要,一个好的名字会让使用者很容易记住,会让使用者莫名产生一种自豪感,会更容易传播;如果名字能够切合某种大道,更能带来顺理成章的效果。本文的监控SDK就叫四维吧,寓意上帝视角。全写four-dimension,简写为FD。

class FourDimension {
    constructor() {
       this.init()
    }

    // 初始化
    init() {
    
    }
}

定义一个 FourDimension 类,目前有构造函数,构造函数无参数。只有init方法,用以初始化类。init方法用以性能、错误、行为数据收集。

二、上报数据方法

业界比较成熟的方案:使用1x1像素的gif图片上报,本文也是。同时也可以使navigator.sendBeaconnavigator.sendBeacon是一个用于发送少量数据到服务器的浏览器API。它有以下几个优点

  1. 异步和非阻塞navigator.sendBeacon 是异步的,它不会阻塞浏览器的其他操作。这对于性能监控来说非常重要,因为都不希望监控的过程影响到页面的性能。

  2. 在页面卸载时仍然可以发送数据:当用户离开页面(例如关闭页面或者导航到其他页面)时,navigator.sendBeacon仍然可以发送数据。这对于捕获和上报页面卸载前的最后一些性能数据来说非常有用。

  3. 低优先级navigator.sendBeacon 发送的请求是低优先级的,它不会影响到页面的其他网络请求。

  4. 简单易用navigator.sendBeacon 的API非常简单,只需要提供上报的URL和数据,就可以发送请求。

与此同时,navigator.sendBeacon 也有一些限制。例如,它只能发送POST请求,不能发送GET请求。而且,它发送的请求没有返回值,不能接收服务器的响应。

最后,一些旧的浏览器可能不支持 navigator.sendBeacon。因此,在使用 navigator.sendBeacon 时,需要根据实际情况进行兼容性处理。

本文的方案是,优先navigator.sendBeacon,降级使用1x1像素gif图片,根据实际情况需要采用xhr。

创建report.js增加上传方法

import {isSupportSendBeacon} from './util'


// 如果浏览器不支持 sendBeacon,就使用图片打点
const sendBeacon = (function(){
    if(isSupportSendBeacon()){
      return window.navigator.sendBeacon.bind(window.navigator)
    }
    const reportImageBeacon = function(url, data){
        reportImage(url, data)
    }
    return reportImageBeacon
})()

export function reportImage(url, data) {
    const img = new Image();
    img.src = url + '?reportData=' + encodeURIComponent(JSON.stringify(data));
}

三、上报时机

参考其它文章,上报时机选择对当前页面影响最小的方案

  1. 上报时机有三种:

  2. 采用 requestIdleCallback/setTimeout 延时上报。

  3. 在 beforeunload 回调函数里上报。

  4. 缓存上报数据,达到一定数量后再上报。

将三种方式结合一起上报:

先缓存上报数据,缓存到一定数量后,利用 requestIdleCallback/setTimeout 延时上报。在页面离开时统一将未上报的数据进行上报。

创建缓存文件cache.js

import { deepCopy } from './util'

const cache = []

export function getCache() {
    return deepCopy(cache)
}

export function addCache(data) {
    cache.push(data)
}

export function clearCache() {
    cache.length = 0
}

其中deepCopy

export function deepCopy(target) {
    if (typeof target === 'object') {
        const result = Array.isArray(target) ? [] : {}
        for (const key in target) {
            if (typeof target[key] == 'object') {
                result[key] = deepCopy(target[key])
            } else {
                result[key] = target[key]
            }
        }

        return result
    }

    return target
}

修改上传文件report.js

import { addCache, getCache, clearCache } from './cache'
import config from '../config'
import {isSupportSendBeacon, generateUniqueID} from './util'

const sendBeacon = (function(){
    if(isSupportSendBeacon()){
      return window.navigator.sendBeacon.bind(window.navigator)
    }
    const reportImageBeacon = function(url, data){
        reportImage(url, data)
    }
    return reportImageBeacon
})()

const sessionID = generateUniqueID()
export function report(data, isImmediate = false) {
    if (!config.reportUrl) {
        console.error('请设置上传 url 地址')
    }

    const reportData = JSON.stringify({
        id: sessionID,
        appID: config.appID,
        userID: config.userID,
        data,
    })

    if (isImmediate) {
        sendBeacon(config.reportUrl, reportData)
        return
    }

    if (window.requestIdleCallback) {
        window.requestIdleCallback(() => {
            sendBeacon(config.reportUrl, reportData)
        }, { timeout: 3000 })
    } else {
        setTimeout(() => {
            sendBeacon(config.reportUrl, reportData)
        })
    }
}

let timer = null
export function lazyReportCache(data, timeout = 3000) {
    addCache(data)

    clearTimeout(timer)
    timer = setTimeout(() => {
        const data = getCache()
        if (data.length) {
            report(data)
            clearCache()
        }
    }, timeout)
}

export function reportWithXHR(data) {
    // 1. 创建 xhr 对象
    let xhr = new XMLHttpRequest()
    // 2. 调用 open 函数
    xhr.open('POST', config.reportUrl)
    // 3. 调用 send 函数
    xhr.send(JSON.stringify(data))
}

export function reportImage(url, data) {
    const img = new Image();
    img.src = url + '?reportData=' + encodeURIComponent(JSON.stringify(data));
}

其中config.js文件

const config = {
    reportUrl: 'http://localhost:8000/reportData',
    projectName: 'fd-example'
}

export function setConfig(options) {
    for (const key in config) {
        if (options[key]) {
            config[key] = options[key]
        }
    }
}
export default config

四、性能数据收集上报

根据最初的规划,性能监控需要收集的数据指标需要有FP、FCP、LCP、DOMContentLoaded、onload、资源加载时间、接口请求时间。

收集FP、FCP、LCP、资源加载时间具体是利用浏览器Performance API。关于Performance API可以参考:Performance[1]

收集上报FP

FP(First Paint)首次绘制,即浏览器开始绘制页面的时间点。这包括了任何用户自定义的绘制,它是渲染任何文本、图像、SVG等的开始时间

import { getPageURL, isSupportPerformanceObserver } from '../utils/util'
import { lazyReportCache } from '../utils/report'

export default function observePaint() {
    if (!isSupportPerformanceObserver()) return
    
    const entryHandler = (list) => {        
        for (const entry of list.getEntries()) {
            if (entry.name === 'first-paint') {
                observer.disconnect()
            }
    
            const json = entry.toJSON()
            delete json.duration
    
            const reportData = {
                ...json,
                subType: entry.name,
                type: 'performance',
                pageURL: getPageURL(),
            }

            lazyReportCache(reportData)
        }
    }
    
    const observer = new PerformanceObserver(entryHandler)
    // buffered 属性表示是否观察缓存数据,也就是说观察代码添加时机比事情触发时机晚也没关系。
    observer.observe({ type: 'paint', buffered: true })

}

代码中observer.disconnect()是PerformanceObserver对象的一个方法,用于停止观察性能指标并断开与回调函数的连接。

事实上

observer.observe({ type: 'paint', buffered: true })

包含两种性能指标:first-contentful-paint和first-paint。

当调用observer.disconnect()方法时,PerformanceObserver对象将停止观察性能指标,并且不再接收任何性能指标的更新。与此同时,与回调函数的连接也会被断开,即使有新的性能指标数据产生,也不会再触发回调函数。

这个方法通常在不再需要观察性能指标时调用,以避免不必要的资源消耗。

收集上报FCP

FCP(First Contentful Paint):首次内容绘制,即浏览器首次绘制DOM内容的时间点,如文本、图像、SVG等。

看起来FCP和FP一致,其实还是有区别的

  • FCP(First Contentful Paint):FCP是指页面上首次渲染任何文本、图像、非空白的canvas或SVG的时间点。它表示了用户首次看到页面有实际内容的时间,即页面开始呈现有意义的内容的时间点。

  • FP(First Paint):FP是指页面上首次渲染任何内容的时间点,包括背景颜色、图片、文本等。它表示了页面开始呈现任何可视化内容的时间,但不一定是有意义的内容。

简而言之,FCP关注的是页面上首次呈现有意义内容的时间点,而FP关注的是页面上首次呈现任何可视化内容的时间点。FCP更关注用户感知的页面加载时间,因为它表示用户可以开始阅读或与页面进行交互的时间点。而FP则更关注页面开始渲染的时间点,无论内容是否有意义

import { getPageURL, isSupportPerformanceObserver } from '../utils/util'
import { lazyReportCache } from '../utils/report'

export default function observePaint() {
    if (!isSupportPerformanceObserver()) return
    
    const entryHandler = (list) => {        
        for (const entry of list.getEntries()) {
            if (entry.name === 'first-contentful-paint') {
                observer.disconnect()
            }
    
            const json = entry.toJSON()
            delete json.duration
    
            const reportData = {
                ...json,
                subType: entry.name,
                type: 'performance',
                pageURL: getPageURL(),
            }

            lazyReportCache(reportData)
        }
    }
    
    const observer = new PerformanceObserver(entryHandler)
    // buffered 属性表示是否观察缓存数据,也就是说观察代码添加时机比事情触发时机晚也没关系。

    observer.observe({ type: 'paint', buffered: true })

}

收集上报LCP

LCP(Largest Contentful Paint):最大内容绘制,即视口中最大的图像或文本块的渲染完成的时间点

import { getPageURL, isSupportPerformanceObserver } from '../utils/util'
import { lazyReportCache } from '../utils/report'

export default function observeLCP() {
    if (!isSupportPerformanceObserver()) {
        return
    }
    
    const entryHandler = (list) => {

        if (observer) {
            observer.disconnect()
        }
        
        for (const entry of list.getEntries()) {
            const json = entry.toJSON()
            delete json.duration

            const reportData = {
                ...json,
                target: entry.element?.tagName,
                name: entry.entryType,
                subType: entry.entryType,
                type: 'performance',
                pageURL: getPageURL(),
            }
            
            lazyReportCache(reportData)
        }
    }

    const observer = new PerformanceObserver(entryHandler)
    observer.observe({ type: 'largest-contentful-paint', buffered: true })
}

收集上报DOMContentLoaded

DOMContentLoaded:当HTML文档被完全加载和解析完成后,DOMContentLoaded事件被触发,无需等待样式表、图像和子框架的完成加载

import { lazyReportCache } from '../utils/report'

export default function observerLoad() {
    ['DOMContentLoaded'].forEach(type => onEvent(type))
}

function onEvent(type) {
    function callback() {
        lazyReportCache({
            type: 'performance',
            subType: type.toLocaleLowerCase(),
            startTime: performance.now(),
        })

        window.removeEventListener(type, callback, true)
    }

    window.addEventListener(type, callback, true)
}

收集上报onload数据

onload:当所有需要立即加载的资源(如图片和样式表)已加载完成时的时间点

import { lazyReportCache } from '../utils/report'

export default function observerLoad() {
    ['load'].forEach(type => onEvent(type))
}

function onEvent(type) {
    function callback() {
        lazyReportCache({
            type: 'performance',
            subType: type.toLocaleLowerCase(),
            startTime: performance.now(),
        })

        window.removeEventListener(type, callback, true)
    }

    window.addEventListener(type, callback, true)
}

收集上报资源加载时间

收集资源加载时间

observer.observe({ type: 'resource', buffered: true })

我在想什么是资源加载时间?应该就是下面的entry.duration的。我觉得写监控SDK很有意义,可以更加深入的学习浏览器模型。了解浏览器是怎么看待各种html文件资源的

import { executeAfterLoad, isSupportPerformanceObserver} from '../utils/util'
import { lazyReportCache } from '../utils/report'

export default function observeEntries() {
    executeAfterLoad(() => {
        observeEvent('resource')
    })
}

export function observeEvent(entryType) {
    function entryHandler(list) {
        const data = list.getEntries()
        for (const entry of data) {
            if (observer) {
                observer.disconnect()
            }

            lazyReportCache({
                name: entry.name, // 资源名称
                subType: entryType,
                type: 'performance',
                sourceType: entry.initiatorType, // 资源类型
                duration: entry.duration, // 资源加载耗时
                dns: entry.domainLookupEnd - entry.domainLookupStart, // DNS 耗时
                tcp: entry.connectEnd - entry.connectStart, // 建立 tcp 连接耗时
                redirect: entry.redirectEnd - entry.redirectStart, // 重定向耗时
                ttfb: entry.responseStart, // 首字节时间
                protocol: entry.nextHopProtocol, // 请求协议
                responseBodySize: entry.encodedBodySize, // 响应内容大小
                responseHeaderSize: entry.transferSize - entry.encodedBodySize, // 响应头部大小
                resourceSize: entry.decodedBodySize, // 资源解压后的大小
                startTime: performance.now(),
            })
        }
    }

    let observer
    if (isSupportPerformanceObserver()) {
        observer = new PerformanceObserver(entryHandler)
        observer.observe({ type: entryType, buffered: true })
    }
}

收集上报接口请求时间

这里通过覆写原生xhr对象方法,对方法做拦截实现接口时间收集以及上报

import { originalOpen, originalSend, originalProto } from '../utils/xhr'
import { lazyReportCache } from '../utils/report'

function overwriteOpenAndSend() {
    originalProto.open = function newOpen(...args) {
        this.url = args[1]
        this.method = args[0]
        originalOpen.apply(this, args)
    }

    originalProto.send = function newSend(...args) {
        this.startTime = Date.now()

        const onLoadend = () => {
            this.endTime = Date.now()
            this.duration = this.endTime - this.startTime

            const { status, duration, startTime, endTime, url, method } = this
            const reportData = {
                status,
                duration,
                startTime,
                endTime,
                url,
                method: (method || 'GET').toUpperCase(),
                success: status >= 200 && status < 300,
                subType: 'xhr',
                type: 'performance',
            }

            lazyReportCache(reportData)
            
            this.removeEventListener('loadend', onLoadend, true)
        }

        this.addEventListener('loadend', onLoadend, true)
        originalSend.apply(this, args)
    }
}

export default function xhr() {
    overwriteOpenAndSend()
}

五、错误数据收集上报

根据最初的规划需要收集资源加载错误、js错误和promise错误。

收集上报资源加载错误

收集 JavaScript、CSS 和图片的加载错误,使用window.addEventListener监听错误

import { lazyReportCache } from '../utils/report'
import { getPageURL } from '../utils/util'

export default function error() {

    // 捕获资源加载失败错误 js css img...
    window.addEventListener('error', e => {
        const target = e.target
        if (!target) return

        if (target.src || target.href) {
            const url = target.src || target.href
            lazyReportCache({
                url,
                type: 'error',
                subType: 'resource',
                startTime: e.timeStamp,
                html: target.outerHTML,
                resourceType: target.tagName,
                paths: e.path.map(item => item.tagName).filter(Boolean),
                pageURL: getPageURL(),
            })
        }
    }, true)
}

收集上报js错误

收集 JavaScript 错误,可以使用 window.onerror 或者 window.addEventListener('error', callback)

import { lazyReportCache } from '../utils/report'
import { getPageURL } from '../utils/util'

export default function error() {

    // 监听 js 错误
    window.onerror = (msg, url, line, column, error) => {
        lazyReportCache({
            msg,
            line,
            column,
            error: error.stack,
            subType: 'js',
            pageURL: url,
            type: 'error',
            startTime: performance.now(),
        })
    }

}

说明一下window.onerror无法捕获资源加载错误,所以这里可以单独拿来监听js错误。

收集上报promise错误

收集 Promise 错误,可以使用 window.addEventListener('unhandledrejection', callback)

import { lazyReportCache } from '../utils/report'
import { getPageURL } from '../utils/util'

export default function error() {

    // 监听 promise 错误 缺点是获取不到列数据
    window.addEventListener('unhandledrejection', e => {
        lazyReportCache({
            reason: e.reason?.stack,
            subType: 'promise',
            type: 'error',
            startTime: e.timeStamp,
            pageURL: getPageURL(),
        })
    })

}

为了减少对html文件代码的干扰,错误收集可以添加一个缓存代理,具体参考字节前端监控实践[2]。

六、行为数据收集上报

根据最初的规划,行为数据收集pv、uv,页面停留时长,用户点击。

收集上报pv、uv

收集 pv(Page View,页面浏览量)和 uv(Unique Visitor,独立访客)数据,需要在每次页面加载时发送一个请求到服务器,然后在服务器端进行统计

import { lazyReportCache } from '../utils/report'
import getUUID from './getUUID'
import { getPageURL } from '../utils/util'

export default function pv() {
    lazyReportCache({
        type: 'behavior',
        subType: 'pv',
        startTime: performance.now(),
        pageURL: getPageURL(),
        referrer: document.referrer,
        uuid: getUUID(),
    })
}

这里只能收集了pv数据,uv数据统计需要在服务端进行。

页面上报停留时长

收集页面停留时长,可以在页面加载时记录一个开始时间,然后在页面卸载时记录一个结束时间,两者的差就是页面的停留时长。这个计算逻辑可以放在beforeunload事件里做

import { report } from '../utils/report'
import { onBeforeunload, getPageURL } from '../utils/util'
import getUUID from './getUUID'

export default function pageAccessDuration() {
    onBeforeunload(() => {
        report({
            type: 'behavior',
            subType: 'page-access-duration',
            startTime: performance.now(),
            pageURL: getPageURL(),
            uuid: getUUID(),
        }, true)
    })
}

用户点击上报

收集用户点击事件,可以使用 addEventListener 来监听 click 事件,这里借助了冒泡

import { lazyReportCache } from '../utils/report'
import { getPageURL } from '../utils/util'
import getUUID from './getUUID'

export default function onClick() {
    ['mousedown', 'touchstart'].forEach(eventType => {
        let timer
        window.addEventListener(eventType, event => {
            clearTimeout(timer)
            timer = setTimeout(() => {
                const target = event.target
                const { top, left } = target.getBoundingClientRect()
                
                lazyReportCache({
                    top,
                    left,
                    eventType,
                    pageHeight: document.documentElement.scrollHeight || document.body.scrollHeight,
                    scrollTop: document.documentElement.scrollTop || document.body.scrollTop,
                    type: 'behavior',
                    subType: 'click',
                    target: target.tagName,
                    paths: event.path?.map(item => item.tagName).filter(Boolean),
                    startTime: event.timeStamp,
                    pageURL: getPageURL(),
                    outerHTML: target.outerHTML,
                    innerHTML: target.innerHTML,
                    width: target.offsetWidth,
                    height: target.offsetHeight,
                    viewport: {
                        width: window.innerWidth,
                        height: window.innerHeight,
                    },
                    uuid: getUUID(),
                })
            }, 500)
        })
    })
}

七、改造完善四维监控类

将性能数据、错误数据、行为数据入口文件的收集方法在监控类四维init方法内初始化

import performance from './performance/index'
import behavior from './behavior/index'
import error from './error/index'

class FourDimension {
    constructor() {
        this.init()
    }
      // 初始化
    init() {
      performance()
      error()
      behavior()
    }
}

new FourDimension().init()

在具体使用过程中,采用异步加载的方式引入。

总结

如果没有具体数据能够证明这个策略是优的,那么就从理论上选优的。这也是我写这篇文章的理论支撑之一。因为毕竟没有真实数据做验证。

还有一个支撑是先模仿,理解别人的再理出自己的思路;而且写文章也是督促自己学习的一种方式。本文大量参考了前端监控 SDK 的一些技术要点原理分析 [3]这篇文章。写着写着发现关键是数据收集和上报方式,具体上报数据模型以及上报方式需要在真实场景中研究迭代。😭

写这篇文章的一个收获:正如前文说的,可以更加深入的了解到浏览器对html文件各种资源是如何看待的。前端开发或者开发这件事一直在和逻辑、数据打交道,但是涉及到底层逻辑的量化指标我认为并不多。而写监控就必须要探索底层的量化指标,这是一个很好的意义可以深入下去的意义。

当然另一个意义也说了,任何事没有反馈则没有进步。监控就是反馈。

另外,我想如果这种监控如果可视化,就如同对人的监控一样,就算是没有警报事件,也能记录被监控对象的各种行为数据。一定会很有有意思。即使没有错误也能有一种可视化画面。

代码地址:github.com/zhensg123/r…[4]

本文完。

可参考文章

前端监控 SDK 的一些技术要点原理分析[5]

一篇讲透自研的前端错误监控[6]

字节前端监控实践[7]

参考资料

[1]

https://developer.mozilla.org/zh-CN/docs/Web/API/Performance: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FPerformance

[2]

https://juejin.cn/post/7195496297150709821#heading-17: https://juejin.cn/post/7195496297150709821#heading-17

[3]

https://juejin.cn/post/7017974567943536671: https://juejin.cn/post/7017974567943536671

[4]

https://github.com/zhensg123/rareRecord/tree/main/fourDemension: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fzhensg123%2FrareRecord%2Ftree%2Fmain%2FfourDemension

[5]

https://juejin.cn/post/7017974567943536671: https://juejin.cn/post/7017974567943536671

[6]

https://juejin.cn/post/6987681953424080926: https://juejin.cn/post/6987681953424080926

[7]

https://juejin.cn/post/7195496297150709821: https://juejin.cn/post/7195496297150709821

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

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

相关文章

msvcp120.dll丢失的6种解决方法,教你如何修复dll文件丢失

“找不到msvcp120dll,无法继续执行代码的6个修复方案”。我相信很多朋友在运行某些程序时&#xff0c;可能会遇到这样的错误提示&#xff1a;“找不到msvcp120dll&#xff0c;无法继续执行代码”。那么&#xff0c;msvcp120dll究竟是什么&#xff1f;为什么会丢失呢&#xff1f…

Java基础知识第四讲:Java 基础 - 深入理解泛型机制

Java 基础 - 深入理解泛型机制 背景&#xff1a;Java泛型这个特性是从JDK 1.5才开始加入的&#xff0c;为了兼容之前的版本&#xff0c;Java泛型的实现采取了“伪泛型”的策略&#xff0c;即Java在语法上支持泛型&#xff0c;但是在编译阶段会进行所谓的“类型擦除”&#xff0…

NestJS——基于Node.js 服务器端应用程序的开发框架

文章目录 前言什么是 NestJS&#xff1f; 一、NestJS特性&#xff1f;二、使用步骤Typescript 知识后端开发基本知识新建项目目录结构 前言 Nestjs中文文档 什么是 NestJS&#xff1f; Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用…

【JVM系列】- 寻觅·方法区的内容

寻觅方法区的内容 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f31d;分享学习心得&#xff0c;欢迎指正&#xff0c;大家一起学习成长&#xff01; 文章目录 寻觅…

杂记 | 使用FRP搭建内网穿透服务(新版toml配置文件,搭配反向代理食用)

文章目录 01 需求与回顾02 下载程序包03 编辑.toml文件3.1 编辑frps.toml3.2 编辑frpc.toml 04 启动服务4.1 启动服务端4.2 启动客户端 05 配置反向代理&#xff08;可选&#xff09;06 windows设置为默认启动&#xff08;可选&#xff09;6.1 创建启动脚本6.2 设置为开机自启 …

【Spring Cloud】声明性REST客户端:Feign

Spring Cloud Feign ——fallback 服务降级 1. Feign 简介2. Feign 的基础使用2.1 普通 HTTP 请求2.2 Feign 远程调用上传文件接口 1. Feign 简介 Feign 是一个声明式的 HTTP 客户端&#xff0c;它简化了编写基于 REST 的服务间通信代码的过程。在 Spring Cloud 中&#xff0c…

你一定要学会的Java语法 -- 【继承】

书接上回&#xff0c;我们已经学完了类和对象&#xff0c;今天内容可能有一点难&#xff0c;相信自己能跨过这道坎。 目录 一. 继承 1.什么是继承 2. 继承的概念 3. 继承的语法 4.父类成员访问 子类和父类成员变量同名 子类和父类成员方法同名 5.super关键字 6.子类构…

POJ 3254 Corn Fields 状态压缩DP(铺砖问题)

一、题目大意 我们要在N * M的田地里种植玉米&#xff0c;有如下限制条件&#xff1a; 1、对已经种植了玉米的位置&#xff0c;它的四个相邻位置都无法继续种植玉米。 2、题目中有说一些块无论如何&#xff0c;都无法种植玉米。 求所有种植玉米的方案数&#xff08;不种植也…

Vector - CANoe - Vector Hardware Manager基础介绍

经常使用CANoe的人都知道&#xff0c;我们之前使用配置VN系列硬件通道的时候使用的是Vector Hardware Config&#xff0c;非常的方便&#xff0c;不过在Vector Driver Setup驱动版本大于22.14后&#xff0c;为了更好的适用车载以太网相关的配置&#xff0c;以及各个配置之间继承…

kubenetes-kubelet组件

一、kubelet架构 每个节点都运行一个kubelet进程&#xff0c;默认监听10250端口&#xff0c;kubelet作用非常重要&#xff0c;是节点的守护神。 接收并执行 master发来的指令。管理Pod及Pod中的容器。每个kubelet进程会在API Server 上注册节点自身信息&#xff0c;定期向mast…

Java学习_对象

对象在计算机中的执行原理 类和对象的一些注意事项 this关键字 构造器 构造器是一种特殊的方法 : 特殊之处在于&#xff0c;名字必须与所在类的名字一样&#xff0c;而且不能写返回值类型 封装 封装的设计规范&#xff1a;合理隐藏、合理暴露 实体类 成员变量和局部变量的区别 …

程序员的护城河:职业发展的关键元素

目录 1. 技术深度与广度 2. 项目经验与实际操作 3. 沟通与团队协作 4. 持续学习与自我更新 5. 社区参与与开源贡献 6. 创新思维与解决问题的能力 7. 职业规划与自我管理 结语 在科技日新月异的今天&#xff0c;程序员的竞争已经不再仅仅依赖于技术水平&#xff0c;而是…

Citespace的使用

CiteSpace CiteSpace的相关介绍运行CiteSpace CiteSpace的相关介绍 CiteSpace作为一款优秀的文献计量学软件&#xff0c;能够将文献之间的关系以科学知识图谱的方式可视化地展现在我们面前。简单来说&#xff0c;面对海量的文献&#xff0c;CiteSpace能够迅速锁定自己需要关注…

使用openvc进行人脸检测:Haar级联分类器

1 人脸检测介绍 1.1 什么是人脸检测 人脸检测的目标是确定图像或视频中是否存在人脸。如果存在多个面&#xff0c;则每个面都被一个边界框包围&#xff0c;因此我们知道这些面的位置 人脸检测算法的主要目标是准确有效地确定图像或视频中人脸的存在和位置。这些算法分析数据…

如何从零开始手写一个消息中间件(从宏观角度理解消息中间件的技术原理)

如何从零开始手写一个消息中间件&#xff08;从宏观角度理解消息中间件的技术原理&#xff09; 什么是消息中间件消息中间件的作用逐一拆解消息中间件的核心技术消息中间件核心技术总览IOBIONIOIO多路复用AIOIO多路复用详细分析selectpollepoll Java中的IO多路复用 协议序列化消…

阿里云严重故障,影响阿里系、淘宝、饿了么、语雀等都崩了...

作者&#xff1a;JavaPub 编程学习一条龙&#xff1a;http://luxian.javapub.net.cn 就在一年一度的双十一剁手节火热进行时&#xff0c;阿里云服务出现了严重故障。 关键是前不久刚发生了语雀事件&#xff0c;不了解的朋友阅读这里 阿里语雀突发P0级事故&#xff0c;一度崩溃…

ASP.NETWeb开发(C#版)-day1-C#基础+实操

目录 .NET实操&#xff1a;创建项目执行 C#基础语法数据类型变量实操001_变量如何在一个解决方案 中创建另一个项目实操002结构实操003-if else实操004-多分支多行注释按钮实操&#xff1a;循环 面向对象基础如何在同一个项目下创建新的.cs文件实操-类的定义与访问实操-练习实操…

基于springboot实现驾校管理系统项目【项目源码】

基于springboot实现驾校管理系统演示 JAVA简介 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&#xff0…

加班把数据库重构完毕

加班把数据库重构完毕 本文的数据库重构是基于 clickhouse 时序非关系型的数据库。该数据库适合存储股票数据&#xff0c;速度快&#xff0c;一般查询都是 ms 级别&#xff0c;不需要异步查询更新界面 ui。 达到目标效果&#xff1a;数据表随便删除&#xff0c;重新拉数据以及指…

基于C#+WPF编写的调用讯飞星火大模型工具

工具源码&#xff1a;https://github.com/lishuangquan1987/XFYun.SparkChat 工具效果截图&#xff1a; 支持流式输出: 其中ApiKey/ApiSecret/AppId需要自己到讯飞星火大模型官网去注册账号申请&#xff0c;免费的。 申请地址&#xff1a;https://xinghuo.xfyun.cn/ 注册之…