让运维无忧,实战解析巡检报告功能实现方案

随着大数据技术的演进和信息安全性需求的提升,数据规模的持续扩张为数据运维工作带来了严峻考验。面对海量数据所形成的繁重管理压力,运维人员面临效率瓶颈,而不断攀升的人力成本也使得单纯依赖扩充运维团队来解决问题变得不再实际可行。

由此可见,智能化与高效便捷是运维发展的必然方向。袋鼠云所推出的巡检报告功能,正是为了顺应这一目标,致力于提供优化的解决方案。

什么是巡检报告?

巡检报告是指对某一个系统或设备进行全面检查,并把检查结果及建议整理成报告的过程。巡检报告通常用于评估系统或设备的运行状况与性能,为发现问题、优化系统、提高效率、降低故障率等方面提供参考。

file

本文将详细阐述巡检报告的各项功能特性和其实现方案,为有此类需求的用户提供实用的参考依据。

巡检报告实现功能

● 自定义布局

· 报告中的面板可进行拖拽改变布局

· 在拖拽的过程中限制拖拽区域,只允许在同一父级内进行拖拽,不允许跨目录移动,不允许改变目录的级别,比如把一级目录移动到另一个一级目录内,变成二级目录

● 目录可收缩展开

· 目录支持收缩展开,收缩时隐藏所有子面板,展开时显示所有子面板

· 移动目录时,子面板跟随移动

· 改变目录后,同步更新右侧的目录面板

· 生成目录编号

file

● 右侧目录树

· 生成目录编号

· 支持锚点滚动

· 支持展开收缩

· 与左侧报告联动

file

● 数据面板

· 根据日期范围获取指标数据

· 通过图表的形式展示指标信息

· 查看详情,删除

· 各面板的请求设计,支持刷新请求

file

file

● 面板导入

· 统计目录下选择的面板数量

· 导入新面板时,不能破坏已有布局,新面板只能跟在旧面板后

· 导入已有面板时,需要进行数据比较,有数据变更需要重新获取最新的数据

file

● 保存

在保存前,所有影响布局相关的操作,都是临时的,包括导入面板。只有在点击保存后,才会把当前数据提交给后端进行保存。

● 支持 pdf 和 word 导出

file

巡检报告实现方案

那么,这一套巡检报告功能究竟是如何实现的呢?下面将从数据结构设计、组件设计、目录、面板等方面进行逐一介绍。

数据结构设计

先看看使用扁平结构下的图示:

file

在扁平结构下,确定子项只需要找到下一个 row 面板,对于多级目录下也是同理,只是对一级目录需要额外处理。

扁平结构虽然实现起来较为简单,但为了满足特定需求,即限制目录的拖拽。限制目录需要一个比较清晰的面板层级关系,很显然,树状数据结构能够非常贴切且清晰地描述一个数据的层级结构。

file

组件设计

与传统组件编程有所区别。在实现上对渲染和数据处理进行了分离,分为两块:

· React 组件:主要负责页面渲染

· Class : 负责数据的处理

file

DashboardModel

class DashboardModel {
    id: string | number;
    panels: PanelModel[]; // 各个面板
    // ...
}

PanelModel

class PanelModel {
    key?: string;
    id!: number;
    gridPos!: GridPos; // 位置信息
    title?: string;
    type: string;
    panels: PanelModel[]; // 目录面板需要维护当前目录下的面板信息
    // ...
}

每一个 Dashboard 组件对应一个 DashboardModel,每一个 Panel 组件对应一个 PanelModel。

React 组建根据类实例中的数据进行渲染。实例生产后,不会轻易的销毁,或者改变引用地址,这让依赖实例数据进行渲染的 React 组件无法触发更新渲染。

需要一个方式,在实例内数据发生改变后,由我们手动触发组件的更新渲染。

● 组件渲染控制

由于我们之前采用的是 Hooks 组件,不像 Class 组件可以通过调用 forceUpdate 方法触发组件。

而在 react18 中有一个新特性 useSyncExternalStore,可以让我们订阅外部的数据,如果数据发生改变了,会触发组件的渲染。

实际上 useSyncExternalStore 触发组件渲染的原理就是在内部维护了一个 state,当更改了 state 值,则引起了外部组件的渲染。

基于这个思路我们简单的实现了一个能够触发组件渲染的 useForceUpdate 方法。

export function useForceUpdate() {
    const [_, setValue] = useState(0);
    return debounce(() => setValue((prevState) => prevState + 1), 0);
}

虽说实现了 useForceUpdate,但是在实际使用的过程中,还需要在组件销毁时移除事件。而 useSyncExternalStore 已经内部已经实现了,直接使用即可。

useSyncExternalStore(dashboard?.subscribe ?? (() => {}), dashboard?.getSnapshot ?? (() => 0));

useSyncExternalStore(panel?.subscribe ?? (() => {}), panel?.getSnapshot ?? (() => 0));

根据 useSyncExternalStore 使用,分别添加了 subscribe 和 getSnapshot 方法。

class DashboardModel {  // PanelModel 一样 
    count = 0;

    forceUpdate() {
        this.count += 1;
        eventEmitter.emit(this.key);
    }

    /**
     * useSyncExternalStore 的第一个入参,执行 listener 可以触发组件的重渲染
     * @param listener
     * @returns
     */
    subscribe = (listener: () => void) => {
        eventEmitter.on(this.key, listener);
        return () => {
            eventEmitter.off(this.key, listener);
        };
    };

    /**
     * useSyncExternalStore 的第二个入参,count 在这里改变后触发diff的通过。
     * @param listener
     * @returns
     */
    getSnapshot = () => {
        return this.count;
    };
}

当改变数据后,需要触发组件的渲染,只需要执行 forceUpdate 即可。

面板

● 面板拖拽

市面上比较大众的拖拽插件有以下几个:

· react-beautiful-dnd

· react-dnd

· react-grid-layout

经过比较后,发现 react-grid-layout 非常适合用来做面板的拖拽功能。react-grid-layout 本身使用简单,基本无上手门槛,最终决定使用 react-grid-layout。详细说明可以查看该链接:https://github.com/react-grid-layout/react-grid-layout

在面板布局改变后触发 react-grid-layout 的 onLayoutChange 方法,可以拿到布局后的所有面板最新的位置数据。

const onLayoutChange = (newLayout: ReactGridLayout.Layout[]) => {
    for (const newPos of newLayout) {
        panelMap[newPos.i!].updateGridPos(newPos);
    }
    dashboard!.sortPanelsByGridPos();
};

PanelMap 是一个 map,key 为 Panel.key, value 为面板,是在我们组件渲染时就已经准备好了。

const panelMap: Record<PanelModel['key'], PanelModel> = {};

要更新面板布局数据,可通过 PanelMap 准确定位到相应的面板,并进一步调用其 updateGridPos 方法执行布局更新操作。

到这,我们只是完成了面板本身数据更新,还需要执行仪表盘的 sortPanelsByGridPos 方法,对所有的面板进行排序。

class DashboardModel {
    sortPanelsByGridPos() {
        this.panels.sort((panelA, panelB) => {
            if (panelA.gridPos.y === panelB.gridPos.y) {
                return panelA.gridPos.x - panelB.gridPos.x;
            } else {
                return panelA.gridPos.y - panelB.gridPos.y;
            }
        });
    }
    // ...
}

● 面板拖动范围

目前的拖动范围是整个仪表盘,可随意拖动,绿色是仪表盘可拖拽区域,灰色为面板。如下:

file

如果需要限制就需要改成如下图的结构:

file

在原本的基础上,以目录为单位区分,绿色为整体的可移动区域,黄色为一级目录块,可在绿色区域拖动,拖动时以整个黄色块进行拖动,紫色为二级目录块,可在当前黄色区域内拖动,不可脱离当前黄色块,灰色的面板只能在当前目录下拖动。

需要在原先数据结构基础上进行改造:

file

class PanelModel {
    dashboard?: DashboardModel; // 当前目录下的 dashboard
    // ...
}

● 面板的导入设计

file

后端返回的数据是一颗有着三级层级的树,我们拿到后,在数据上维护成 ModuleMap, DashboardMap 和 PanelMap 3个Map。

import { createContext } from 'react';

export interface Module { // 一级目录
    key: string;
    label: string;
    dashboards?: string[];
    sub_module?: Dashboard[];
}

export interface Dashboard { // 二级目录
    key: string;
    dashboard_key: string;
    label: string;
    panels?: number[];
    selectPanels?: number[];
    metrics?: Panel[];
}

export interface Panel {
    expr: Expr[]; // 数据源语句信息
    label: string;
    panel_id: number;
}

type Expr = {
    expr: string;
    legendFormat: string;
};

export const DashboardContext = createContext({
    moduleMap: new Map<string, Module>(),
    dashboardMap: new Map<string, Dashboard>(),
    panelMap: new Map<number, Panel>(),
});

我们在渲染模块时,遍历 ModuleMap ,并通过 Module 内的 dashboards 信息找到二级目录。

在交互上设置一级目录不可选中,当选中二级目录时,通过二级目录 Dashboard 的 panels 找到相关的面板渲染到右侧区域。

对于这3个 Map 的操作,维护在 useHandleData 中,导出:

{
    ...map, // moduleMap、dashboardMap、panelMap
    getData, // 生成巡检报告的数据结构
    init: initData, // 初始化 Map
}

● 面板选中回填

在进入面板管理时,需要回填已选中的面板,我们可以通过 getSaveModel 获取到当前巡检报告的信息,把对应的选中信息存放到 selectPanels 中。

现在我们只需要改变 selectPanels 中的值,就可以做到对应面板的选中。

● 面板选中重置

直接遍历 DashboardMap,并把每个 selectPanels 重置。

dashboardMap.forEach((dashboard) => {
    dashboard.selectPanels = [];
});

● 面板插入

在我们选中面板后,对选中面板进行插入时,有几种情况:

· 巡检报告原本存在的面板,这次也选中,在插入时会比较数据,如果数据发生改变,需要根据最新的数据源信息进行请求,并渲染

· 巡检报告原本存在的面板,这次未选中,在插入时,需要删除掉未选中的面板

· 新选中的面板,在插入时,在对应目录的末尾进行插入

添加新面板需要,与目录收缩类似,不同的是:

· 目录收缩针对的只有一个目录,而插入在针对的是整体

· 目录收缩是直接从子节点开始向上冒泡,而插入是先从根节点开始向下插入,插入完成后在根据最新的目录数据,更新一遍布局

class DashboardModel {
    update(panels: PanelData[]) {
        this.updatePanels(panels); // 更新面板
        this.resetDashboardGridPos(); // 重新布局
        this.forceUpdate();
    }

    /**
     * 以当前与传入的进行对比,以传入的数据为准,并在当前的顺序上进行修改
     * @param panels
     */
    updatePanels(panels: PanelData[]) {
        const panelMap = new Map();
        panels.forEach((panel) => panelMap.set(panel.id, panel));

        this.panels = this.panels.filter((panel) => {
            if (panelMap.has(panel.id)) {
                panel.update(panelMap.get(panel.id));
                panelMap.delete(panel.id);
                return true;
            }
            return false;
        });

        panelMap.forEach((panel) => {
            this.addPanel(panel);
        });
    }

    addPanel(panelData: any) {
        this.panels = [...this.panels, new PanelModel({ ...panelData, top: this })];
    }

    resetDashboardGridPos(panels: PanelModel[] = this.panels) {
        let sumH = 0;
        panels?.forEach((panel: any | PanelModel) => {
            let h = ROW_HEIGHT;
            if (isRowPanel(panel)) {
                h += this.resetDashboardGridPos(panel.dashboard.panels);
            } else {
                h = panel.getHeight();
            }

            const gridPos = {
                ...panel.gridPos,
                y: sumH,
                h,
            };
            panel.updateGridPos({ ...gridPos });
            sumH += h;
        });

        return sumH;
    }
}

class PanelModel {
    /**
     * 更新
     * @param panel
     */
    update(panel: PanelData) {
        // 数据源语句发生变化需要重新获取数据
        if (this.target !== panel.target) {
            this.needRequest = true;
        }

        this.restoreModel(panel);

        if (this.dashboard) {
            this.dashboard.updatePanels(panel.panels ?? []);
        }

        this.needRequest && this.forceUpdate();
    }
}

● 面板请求

needRequest 控制面板是否需要进行请求,如果为 true 在面板下一次进行渲染时,会进行请求,请求的处理也放在了 PanelModel 中。

import { Params, params as fetchParams } from '../../components/useParams';

class PanelModel {
    target: string; // 数据源信息

    getParams() {
        return {
            targets: this.target,
            ...fetchParams,
        } as Params;
    }

    request = () => {
        if (!this.needRequest) return;
        this.fetchData(this.getParams());
    };

    fetchData = async (params: Params) => {
        const data = await this.fetch(params);
        this.data = data;
        this.needRequest = false;
        this.forceUpdate();
    };

    fetch = async (params: Params) => { /* ... */ }
}

我们数据渲染组件一般层级较深,而请求时会需要时间区间等外部参数,对于这部分参数采用全局变量的方式,用 useParams 进行维护。上层组件使用 change 修改参数,数据渲染组件根据抛出的 params 进行请求。

export let params: Params = {
    decimal: 1,
    unit: null,
};

function useParams() {
    const change = (next: (() => Params) | Params) => {
        if (typeof next === 'function') params = next();
        params = { ...params, ...next } as Params;
    };

    return { params, change };
}

export default useParams;

● 面板刷新

从根节点向下查找,找到叶子节点,在触发对应的请求。

file

class DashboardModel {
    /**
     * 刷新子面板
     */
    reloadPanels() {
        this.panels.forEach((panel) => {
            panel.reload();
        });
    }
}

class PanelModel {
    /**
     * 刷新
     */
    reload() {
        if (isRowPanel(this)) {
            this.dashboard.reloadPanels();
        } else {
            this.reRequest();
        }
    }

    reRequest() {
        this.needRequest = true;
        this.request();
    }
}

● 面板的删除

对于面板的删除,我们只需要在对应的 Dashboard 下进行移除,删除后会改变当前 Dashboard 高度,这块的处理与下文的目录收缩一致。

class DashboardModel {
    /**
     * @param panel 删除的面板
     */
    removePanel(panel: PanelModel) {
        this.panels = this.filterPanelsByPanels([panel]);

        // 冒泡父容器,减少的高度
        const h = -panel.gridPos.h;
        this.top?.changeHeight(h);

        this.forceUpdate();
    }

    /**
     * 根据传入的面板进行过滤
     * @param panels 需要过滤的面板数组
     * @returns 过滤后的面板
     */
    filterPanelsByPanels(panels: PanelModel[]) {
        return this.panels.filter((panel) => !panels.includes(panel));
    }
    // ...
}

● 面板的保存

与后端沟通后,当前巡检报告数据结构由前端自主维护,最终给后端一个字符串就好。获取到目前的面板数据,用 JSON 进行转换即可。

面板的信息获取过程,先从根节点出发,遍历至叶子结点,再从叶子结点开始,一层层向上进行返回,也就是回溯的过程。

class DashboardModel {
    /**
     * 获取所有面板数据
     * @returns
     */
    getSaveModel() {
        const panels: PanelData[] = this.panels.map((panel) => panel.getSaveModel());
        return panels;
    }
    // ...
}

// 最终保存时所需要的属性,其他的都不需要
const persistedProperties: { [str: string]: boolean } = {
    id: true,
    title: true,
    type: true,
    gridPos: true,
    collapsed: true,
    target: true,
};

class PanelModel {
    /**
     * 获取所有面板数据
     * @returns
     */
    getSaveModel() {
        const model: any = {};

        for (const property in this) {
            if (persistedProperties[property] && this.hasOwnProperty(property)) {
                model[property] = cloneDeep(this[property]);
            }
        }
        model.panels = this.dashboard?.getSaveModel() ?? [];

        return model;
    }
    // ...
}

● 面板详情展示

file

对面板进行查看时,可修改时间等,这些操作会影响到实例中的数据,需要对原数据与详情中的数据进行区分。

通过对原面板数据的重新生成一个 PanelModel 实例,对这个实例进行任意操作,都不会影响到原数据。

const model = panel.getSaveModel();
const newPanel = new PanelModel({ ...model, top: panel.top }); // 创建一个新的实例
setEditPanel(newPanel); // 设置为详情

在 dom 上,详情页面是采用绝对定位,覆盖着巡检报告。

目录

● 目录收缩展开

为目录面板维护一个 collapsed 属性用来控制面板的隐藏显示。

class PanelModel {
    collapsed?: boolean; // type = row
    // ...
}

// 组件渲染
{!collapsed && <DashBoard dashboard={panel.dashboard} serialNumber={serialNumber} />}

对目录进行收缩展开时,会改变自身的高度,现在还需要把这个改变的高度同步给上一级的仪表盘。

上一级需要做的就是类似我们控制目录的处理。如下,控制第一个二级目录收缩:

file

当面板发生变更时,需要通知上级面板,进行对应的操作。

file

增加一个 top 用来获取到父级实例。

class DashboardModel {
    top?: null | PanelModel; // 最近的 panel 面板

    /**
     * 面板高度变更,同步修改其他面板进行对应高度 Y 轴的变更
     * @param row 变更高度的 row 面板
     * @param h 变更高度
     */
    togglePanelHeight(row: PanelModel, h: number) {
        const rowIndex = this.getIndexById(row.id);

        for (let panelIndex = rowIndex + 1; panelIndex < this.panels.length; panelIndex++) {
            this.panels[panelIndex].gridPos.y += h;
        }
        this.panels = [...this.panels];

        // 顶级 dashBoard 容器没有 top
        this.top?.changeHeight(h);
        this.forceUpdate();
    }
    // ...
}

class PanelModel {
    top: DashboardModel; // 最近的 dashboard 面板

    /**
     * @returns h 展开收起影响的高度
     */
    toggleRow() {
        this.collapsed = !this.collapsed;
        let h = this.dashboard?.getHeight();
        h = this.collapsed ? -h : h;
        this.changeHeight(h);
    }

    /**
     *
     * @param h 变更的高度
     */
    changeHeight(h: number) {
        this.updateGridPos({ ...this.gridPos, h: this.gridPos.h + h }); // 更改自身面板的高度
        this.top.togglePanelHeight(this, h); // 触发父级变更
        this.forceUpdate();
    }
    // ...
}

整理流程与冒泡类型,一直到最顶级的 Dashboard。展开收缩同理。

file

● 右侧目录渲染

锚点/序号

· 锚点采用 Anchor + id 选中组件

· 序号根据每次渲染进行生成

采用发布订阅管理渲染

每当仪表盘改变布局的动作时,右侧目录就需要进行同步更新,而任意一个面板都有可能需要触发右侧目录的更新。

如果我们采用实例内维护对应组件的渲染事件,有两个问题:

· 需要进行区分,比如刷新面板时,不需要触发右侧目录的渲染

· 每个面板如何订阅右侧目录的渲染事件

最终采用了发布订阅者模式,对事件进行管理。

class EventEmitter {
    list: Record<string, any[]> = {};

    /**
     * 订阅
     * @param event 订阅事件
     * @param fn 订阅事件回调
     * @returns
     */
    on(event: string, fn: () => void) {}

    /**
     * 取消订阅
     * @param event 订阅事件
     * @param fn 订阅事件回调
     * @returns
     */
    off(event: string, fn: () => void) {}

    /**
     * 发布
     * @param event 订阅事件
     * @param arg 额外参数
     * @returns
     */
    emit(event: string, ...arg: any[]) {
}
eventEmitter.emit(this.key); // 触发面板的订阅事件

eventEmitter.emit(GLOBAL); // 触发顶级订阅事件,就包括右侧目录的更新

pdf/word 导出

pdf 导出由 html2Canvas + jsPDF 实现。需要注意的是,当图片过长 pdf 会对图片进行切分,有可能出现切分的是内容区域的情况。需要手动计算面板的高度,是否超出当前文档,如果超出需要我们提前进行分割,添加到下一页中,尽可能把目录面板和数据面板一块切分。

word 导出由 html-docx-js 实现, 需要保留目录的结构,并可以在面板下添加总结,这就需要我们分别对每一个面板进行图片的转换。

实现的思路是根据 panels 遍历,找到目录面板就是用 h1、h2 标签插入,如果是数据面板,在数据面板中维护一个 ref 的属性,能让我们拿到当前面板的 dom 信息,根据这个进行图片转换,并为 base64 的格式(word 只支持 base64 的图片插入)。

写在最后

当前版本的巡检报告尚处于初级阶段,并非最终形态,随着后续的迭代升级,我们将逐步添加包括总结说明在内的多项功能。

采用目前方式实现后,未来若需进行 UI 界面调整时,只需针对性地修改相关 UI 组件即可,例如新增饼图、表格等内容。而在数据交互层面的改动,则仅需进入 DashboardModel 和 PanelModel 中进行必要的更新。此外,针对特定场景,我们还可以灵活抽离出专用类来进行处理,以确保整个迭代过程更加模块化和高效化。

《数栈产品白皮书》下载地址:https://www.dtstack.com/resources/1004?src=szsm

《数据治理行业实践白皮书》下载地址:https://www.dtstack.com/resources/1001?src=szsm

想了解或咨询更多有关大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:https://www.dtstack.com/?src=szcsdn

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

黄金投资是收益高还是风险高?

黄金作为一种传统的投资工具&#xff0c;长久以来一直受到投资者的青睐。然而&#xff0c;在讨论黄金投资的收益与风险时&#xff0c;必须明确一点&#xff1a;黄金投资既有可能带来较高的收益&#xff0c;同时也伴随不可忽视的风险。 从收益的角度来看&#xff0c;黄金投资的确…

Linux上轻松搞定Docker环境安装

Docker环境安装 是否安装docker # 该命令通过查询Docker服务的状态来检查是否已安装&#xff0c;且是否在正常运行 systemctl status docker下面这种状态就是docker正常运行的状态&#xff1a; 安装yum-utils&#xff1a; yum install ‐y yum‐utils device‐mapper‐per…

手工将一个 llvm IR 汇编代码解析成为 bitcode 文件

1&#xff0c;原始c语言文件 sum.c int sum(int a, int b) {return ab; } 2&#xff0c;编译成为 LLVM-IR 汇编语言 clang sum.c -emit-llvm -S -c -o sum.ll 3&#xff0c;手工把 llvm IR 汇编语言解析成 bitcode 3.1&#xff0c;源码 gen_llvm_ir.cpp #include <ll…

C++初阶:初识C++

目录 1. 前言&#xff1a;C 与 C语言2. C对于C语言语法的完善与补充2.1 命名冲突与命名空间2.1.1 命名空间的定义2.1.2 调用方式 2.3 补充&#xff1a;流的概念2.4 缺省参数2.4.1 缺省参数的使用 2.5 函数重载2.5.1 什么是函数重载2.5.2 函数重载的使用2.5.3 特殊情况&#xff…

蓝桥杯-Set

目录 HashSet类常用方法 1 add(Object obj)方法 2 size() 方法 3 remove(Object obj)方法 4 contains()方法 5 clear() 方法 例题实战 set 一个不允许出现重复的元素&#xff0c;并且无需的集合&#xff0c;主要有HashSet实现类。 在判断重复元素的时候&#xff0c;Set…

2024年我强烈建议你一定要入局鸿蒙

随着华为鸿蒙系统的诞生&#xff0c;它一直备受程序员及全国人民深度关注。对于那些对鸿蒙开发感兴趣并希望在这一领域寻找职业发展的人来说&#xff0c;2024年学鸿蒙开发的就业前景如何呢&#xff1f; 在万物互联、技术发展飞快的时代&#xff0c;鸿蒙对于程序员和技术人员而…

MySQL 篇-深入了解多表设计、多表查询

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 多表设计概述 1.1 多表设计 - 一对多 1.2 多表设计 - 一对一 1.3 多表设计 - 多对多 2.0 多表查询概述 2.1 多表查询 - 内连接 2.2 多表查询 - 外连接 2.3 多表查…

激光炸弹c++

题目 输入样例&#xff1a; 2 1 0 0 1 1 1 1输出样例&#xff1a; 1 思路 由题知本题要求某个区间内数的和&#xff0c;联想到二维前缀和。我们可以先使用二维前缀和模板计算各区间的价值。然后枚举以某点为右下角&#xff0c;大小为R*R的正方形价值&#xff0c;取最大值。 …

C# LaMa Image Inpainting 图像修复 Onnx Demo

目录 介绍 效果 模型信息 项目 代码 下载 LaMa Image Inpainting 图像修复 Onnx Demo 介绍 gihub地址&#xff1a;https://github.com/advimman/lama &#x1f999; LaMa Image Inpainting, Resolution-robust Large Mask Inpainting with Fourier Convolutions, WAC…

双体系Java学习之关键字,标识符以及命名规范

重新开始从Java基础开始学&#xff0c;保持每周两更的状态&#xff0c;刚开学事情有点多。 关键字 标识符 命名规范

不用下载的工具却能保存西瓜视频的原画视频,支持无水印!

近年来&#xff0c;西瓜视频可谓是炙手可热&#xff0c;得益于其强大的后盾——抖音&#xff0c;以及推出的"中视频计划"。这个计划慷慨地斥资20亿用于支持视频制作者&#xff0c;因此在西瓜视频平台上&#xff0c;我们目睹了大量优质的长视频如雨后春笋般涌现。 对于…

云计算 3月6号 (系统中发送邮件)

系统中发送邮件 linux 系统中自带了内部邮件系统&#xff0c;可以通过mail命令进行邮件发送及接受 # 安装mailx yum install -y mailx 1.1 发送邮件给系统用户 # 方式1 mail -s "邮件标题" 收件人 邮件内容 ctrl d 结束发送 ​ # 方式2 echo 内容 | mail -s "…

SQL中如何添加数据

SQL中如何添加数据 一、SQL中如何添加数据&#xff08;方法汇总&#xff09;二、SQL中如何添加数据&#xff08;方法详细解说&#xff09;1. 使用SQL脚本&#xff08;推荐&#xff09;1.1 在表中插入1.1.1 **第一种形式**1.1.2 **第二种形式**SQL INSERT INTO 语法示例SQL INSE…

linux实现远程文件夹共享-samba

目录 问题描述Samba如何挂载常用参数临时挂载实例一种长期挂载方法&#xff08;已失败&#xff0c;仅供参考&#xff09;查看挂载取消挂载umount失败 问题描述 我的代码需要访问存在于两个系统&#xff08;win和linux&#xff09;的文件夹&#xff0c;我不是文件夹的创建者&am…

【高效开发工具系列】vimdiff简介与使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Vant组件中van-overlay页面太长无法滚动

内容太长&#xff0c;发现电脑中滚轮可以滚动&#xff0c;但是手机端手指滑动动不了。 在组件上加lock-scroll <van-overlay :lock-scroll"false"> 默认为true 注&#xff1a;我使用的版本是4.8.5&#xff0c;据说2版本不生效。

TinyEMU编译与使用

TinyEMU编译与使用 1 介绍2 准备工作3 编译TinyEMU3.1 安装依赖库3.2 编译 4 运行TinyEMU4.1 在线运行4.2 离线运行 5 共享目录5.1 修改root_9p-riscv64.cfg5.2 启动TinyEMU5.3 执行挂载命令 6 TinyEMU命令帮助 1 介绍 原名为riscvemu&#xff0c;于2018-09-23&#xff0c;改为…

Windows安装Go语言及VScode配置

最近搞自己的网站时突然想起来很多上学时的事&#xff0c;那会美国总统还是奥巴马&#xff0c;网页课教的是DreamWeaver跟Photoshop&#xff0c;其他语言像PHP、Java8、Python都有学一点&#xff0c;讲究一个所见即所得。虽然是信管专业那时和斌桑班长对新语言很感兴趣&#xf…

企业级Avatar道具解决方案

美摄科技&#xff0c;作为业界领先的多媒体解决方案提供商&#xff0c;近日推出了一款革命性的Avatar道具解决方案&#xff0c;旨在帮助企业打造独特且高度个性化的数字形象&#xff0c;从而提升企业品牌的吸引力和影响力。 这款解决方案的核心在于其先进的单摄像头Avatar生成…

C++ 位运算OJ

目录 位运算常用操作&#xff1a; 1、 191. 位1的个数 2、 338. 比特位计数 3、 461. 汉明距离 4、136. 只出现一次的数字 5、 260. 只出现一次的数字 III 6、面试题 01.01. 判定字符是否唯一 7、 268. 丢失的数字 8、 371. 两整数之和 9、 137. 只出现一次的数字 II …