Monaco Editor系列(一)启动项目与入门示例解析

news2024/11/23 2:02:11

前言:作为一名程序员,我们工作中的每一天都在与代码编辑器打交道,相信各位前端程序员对 VS Code 一定都不陌生,VS Code 可以为我们提供代码高亮、代码对比等等功能,让我们在开发的时候,不需要对着暗淡无光的脚本编辑,而是对着五彩缤纷的界面编辑,并且一个右键或者快捷键,编辑器就可以帮我们实现很多的功能。这个系列我讲述的是 VS Code 的 弟弟 – Monaco Editor,它是一款基础代码编辑器,VS Code 内部的代码编辑模块和它是一样一样的,只不过 VS Code 比它增加了文件管理系统,可以通过Node.js操作文件。
另外,Monaco Editor 特别有出息,市面上很多的浏览器端的代码编辑器都有 Monaco Editor 的身影,例如 github的浏览器端代码编辑器
在这里插入图片描述
等等我就不举例啦
为什么要学习 Monaco Editor ?
对于我自己来说,是折服于代码编辑器的丝滑体验和强大功能,好奇内部是怎么实现的,另外就是通过学习这么一个优秀的项目的源码,学习优秀的编码思维和编码技巧。
下面迫不及待的进入 Monaco Editor 的学习吧!
环境说明
🥗 Macbook Apple芯片
🥗 Node16
🥗 编辑器 idea2023 (虽然我讲了 vs code 的弟弟,但我用的是 idea 哈哈哈,主要是平时开发前后端不分离,用习惯了🤣)

一、启动项目

1、下载源码

源码地址:github源码地址
既然我们要学习Monaco Editor的源码,就需要把源码下载下来,而不是使用 npm 安装,那样只会作为依赖出现在项目里,而现如今,Monaco Editor 从依赖变成了研究对象,转正了!
安装方式有两种,一种是在命令行使用 git clone https://github.com/microsoft/monaco-editor.git,安装,但是我的网络不争气,下载不成功,所以只能用笨蛋方法,下载压缩包,我的版本是 0.46.0
在这里插入图片描述

2、使用编辑器打开项目,在项目的根目录下运行 npm i . 安装依赖
3、编译本地项目

package.json 中有编译项目的命令的定义
在这里插入图片描述
运行 npm run build-monaco-editor ,会构建出来一个本地的 monaco-editor。

4、进入本地的 monaco-editor 目录: cd website
5、在当前 /website 目录安装依赖:npm i .
6、然后要编译typescript,根据 package.json 中的命令定义,运行 npm run typedoc

在这里插入图片描述
此处我运行之后报错了
在这里插入图片描述
omg,源码也出错吗?我产生了深深的怀疑,于是就怀疑自己的Node.js的版本不兼容,为此特地下载了 nvm 工具切换了好几个版本的 Node,报错依然如旧,好嘛,还是老老实实分析一下代码。这里的报错就是说有几个变量是undefined,所以取不到它的name属性,typescript 使用对象时提供了安全保护,只需要增加一个 ? ,就可以规避对象不存在的风险,所以这里就是给出错的几个可能不存在的对象后面加一个 ?
在这里插入图片描述
然后在运行 npm run typedoc 就成功了,并且 typedoc 文件夹中多出来了一个 dist 目录,里面存放的应该就是将 typescript 编译之后的结果咯,
在这里插入图片描述

7、启动项目

运行 npm run dev
然而此时又出现了一丢丢的报错,不要慌!定睛一看,是在说 webpack.config.ts 里面的插件的引入方法不对
在这里插入图片描述
改成
import CopyPlugin from "copy-webpack-plugin";
这两个都需要改一下引入方式,改成下面这种,虽然飘红,但是能用
在这里插入图片描述
就可以啦!!
npm run dev 成功!!
本地的 Monaco Editor 就是介个样子的啦
在这里插入图片描述

二、入口文件

1、寻找入口文件

咱们这个项目使用 webpack 构建的,这么一堆代码,该从哪里开始看呢?其实就是从 webpack.config.ts 开始,因为它会告诉我们谁是入口文件!

entry: {
	index: r("src/website/index.tsx"),
	playgroundRunner: r("src/runner/index.ts"),
	monacoLoader: r("src/website/monaco-loader-chunk.ts"),
},

根据 webpack.config.ts 的配置我们可以知道,入口文件就是 src/website/index.tsx。咦,文件路径前面的 r 是什么意思呢?其实是定义的读取文件的方法,这也是我们在实际开发中可以借鉴的一个小技巧

const r = (file: string) => path.resolve(__dirname, file);

关于 chunk 的理解,可以参考文章:webpack 理解 chunk
webpack 的作用就是将我们写的代码模块 module 打包在一起,模块就是我们编写的所有文件。chunk 就是 webpack 在打包的过程中,一些 module 的集合。Webpack 的打包是通过入口文件开始的,入口文件会引用其他模块,其他模块还会引用其他模块,webpack 从入口文件开始,把一系列引用的模块打包为一个 chunk。
当前的入口是对象的形式,每一个属性都会生成一个 chunk,属性名会被当成chunk的名称。
从这里的 entry 我们可以看到,当前的项目是一个多页面应用,每个页面都需要通过 HtmlWebpackPlugin 插件生成页面模版文件,生成页面模版文件的代码在下面的 plugins 配置项里面,这里我们先只关注 index chunk 下的配置

new HtmlWebpackPlugin({
    chunks: ["monacoLoader", "index"],
    templateContent: getHtml(),
    chunksSortMode: "manual",  // 控制排序:手动,就是根据 chunks 数组的顺序依次引入 
}),
new HtmlWebpackPlugin({
    chunks: ["index"],
    filename: "playground.html",
    templateContent: getHtml(),
}),
 new HtmlWebpackPlugin({
    chunks: ["index"],
    filename: "docs.html",
    templateContent: getHtml(),
}),
new HtmlWebpackPlugin({
    chunks: ["index"],
    filename: "monarch.html",
    templateContent: getHtml(),
}),

html-webpack-plugin 插件可以为应用程序生成 HTML 文件,具体的配置项可以查看 插件文档。

  • chunks:这个属性主要在多页面应用的时候才有用哦!webpack 打包的过程中会生成多个 chunk,这些 chunk 其实是 js 文件,使用 chunks 属性就可以指定当前生成的这个 HTML 使用哪一个 js 文件
  • filename:生成的html文件的文件名,默认为 index.html
  • templateContent:模版文件
  • chunksSortMode:是用来控制 HTML 所引用的 chunk 的顺序,可选项:‘none’ | ‘auto’ | ‘manual’ | {Function}。详细的含义就不解释了,这里使用的 manual 就是按照 chunks 数组的顺序引入资源

以第一个 HtmlWebpackPlugin 为例,这个文件没有设置 filename 属性,生成的就是根路由对应的页面,也就是请求 localhost 根页面返回的 html 文件,这个文件需要依赖两个 chunk ,其实就是要依次执行这两个 chunk 对应的入口文件中的代码。
第一个是 monacoLoader chunk,它的入口文件是 src/website/monaco-loader-chunk.ts,为了验证,我们可以在里面打印 111
在这里插入图片描述

第二个是 index chunk ,对应的入口文件是 src/website/index.tsx,为了验证我们也打印 222
在这里插入图片描述

然后我们访问根目录,在控制台中的 network 栏,可以看到,第一个请求就是请求的 localhost 的根页面,页面会通过 script 标签依次引入上述的两个 chunk 生成的 js 文件

在这里插入图片描述

另外,我们看一下控制台输出,果然不出我所料,输出了 111 和 222

在这里插入图片描述

引入的几个 js 文件,就是 webpack 打包之后的结果,可以在 sources 栏看到

在这里插入图片描述

2、查看入口文件

index chunk 的入口文件的内容如下,我稍微做了一点小注释:
src/website/index.tsx

// 引入react
import * as React from "react";
import * as ReactDOM from "react-dom";
// 引入样式文件
import "./bootstrap.scss";
import "./style.scss";
// 引入应用
import { App } from "./pages/App";

// 创建 root 容器
const elem = document.createElement("div");
elem.className = "root";
// 插入到文档中
document.body.append(elem);
// 将应用渲染到 root 中
ReactDOM.render(<App />, elem);
3、应用的定义

接下来我们顺藤摸瓜,看一下 App 应用的定义吧!
src/website/pages/App.tsx

// 引入 Home 组件
import { Home } from "./home/Home";
// 引入 PlaygroundPage 组件
import { PlaygroundPage } from "./playground/PlaygroundPage";
// 引入 路由
import { docs, home, monarch, playground } from "./routes";
// 引入 React
import React = require("react");
// 引入 DocsPage
import { DocsPage } from "./DocsPage";
// 引入 MonarchPage
import { MonarchPage } from "./MonarchPage";

export class App extends React.Component {
	// 根据路由返回指定的组件
	render() {
		if (home.isActive) {
			return <Home />;
		} else if (playground.isActive) {
			return <PlaygroundPage />;
		} else if (docs.isActive) {
			return <DocsPage />;
		} else if (monarch.isActive) {
			return <MonarchPage />;
		}
		return <>Page does not exist</>;
	}
}

App 干的最主要的事情,就是根据路由返回指定的页面。
插一句题外话,我发现我的项目执行 npm run dev 没有自动在浏览器中打开,如此不智能,还需要我动用发财的小手亲自点开链接,不可原谅!我们只需要在 webpack.config.ts 的 devServer 配置项中增加一个 open:true 配置就可以让我们的 Monaco 变聪明啦!另外还可以加上 hot:true,就可以在我们更改代码的时候,自动刷新浏览器中网页的内容。

devServer: {
    open: true,
    hot: true,
    //...
}
4、路由的配置

接下来我们来看一看路由的配置
src/website/pages/routes.ts

// 定义路由类
export class Route {
	// Route 接收 href 为参数
	constructor(public readonly href: string) {}

	// 路由实例上的属性
	// get 语法 访问对象的isActive属性时,得到的是 isActive() 方法的返回值
	// isActive 通过url路径判断当前访问的url是对应哪一个路由
	get isActive(): boolean {
		const target = new URL(this.href, window.location.href);
		return (
			trimEnd(target.pathname, ".html") ===
			trimEnd(window.location.pathname, ".html")
		);
	}
}

function trimEnd(str: string, end: string): string {
	if (str.endsWith(end)) {
		return str.substring(0, str.length - end.length);
	}
	return str;
}

// 通过new创建路由实例
export const home = new Route("./");
export const playground = new Route("./playground.html");
export const docs = new Route("./docs.html");
export const monarch = new Route("./monarch.html");

从路由配置和 App 的配置中可以看出,根路径对应的就是 Home 组件,/playground.html 路径对应的就是 Playground 组件,然后我们现在访问一下 /playground.html 路由,记得加后面的 .html 哦。就会展示代码编辑页面

在这里插入图片描述

三、Hello World案例

进入 Playground 组件,映入眼帘的就是 Monaco 给我们提供的 Hello World 案例,左侧是源码,右侧是展示效果。

1、左侧源码

怎么找代码的位置呢?咱们直接复制代码,全局搜索,这个 hello-world 文件夹就是
在这里插入图片描述

左侧区域的三个编辑区域就分别是该文件夹中的 js、html、css 文件的内容
在这里插入图片描述
源码定义的部分找到了,但是它是怎么来到编辑框里的呢?

其实找源码这个过程可以有两条路,一条是通过 html 中的类名,找模版中的定义,一条是通过 /hello-world 这个文件夹的路径找,因为肯定有地方通过 import 引入了这几个文件。 实不相瞒,第一条路我走过了,有点曲折,所以直接说第二条路。

通过全局搜索 /hello world 可以找到website/src/website/data/playground-samples/all.js 中引入了所有示例文件定义的数据,并且将这些文件和对应的内容定义为json文件,放到了变量 PLAY_SAMPLES 中。
那么此时我们再全局搜索一下 all.js 路径,看看哪个地方引入了这个文件,好吧,并没有找到。不要心灰,不要失望,这是全局搜索一下它的父文件夹 playground-samples,就会发现一个可疑的目标website/src/website/pages/playground/playgroundExamples.tsx

在这个文件里,调用了 require.context() 方法,这个方法是 Webpack 的api,用来检索目录内容,

// require.context:
// 参数一:表示检索的目录
// 参数二:表示是否检索子文件夹
// 参数三:匹配文件的正则表达式

const descriptions = require.context<{ title: string; sortingKey?: number }>(
	"../../data/playground-samples",
	true,
	/json$/
);

这是把所有的 json 文件都检索了一遍,我们在控制台看一下输出,发现返回值是一个方法
在这里插入图片描述
返回值可以通过 .keys().forEach() 的方式遍历文件夹中的所有文件

descriptions.keys().forEach(item=>{
	console.log(item)
})

打印一下发现是文件夹中所有 json 文件的路径
在这里插入图片描述

website/src/website/pages/playground/playgroundExamples.tsx 文件最后导出了一个方法 getPlaygroundExamples() ,这个方法最后有一个返回值 result 。中间的具体过程先不看,先打印一下这个 result,是一个下图所示,保存了所有的示例数据的数组
在这里插入图片描述
虽然这里面并没有 html 、js 、 css 文件内容 🥹,但是有一个 load() 方法
website/src/website/pages/playground/playgroundExamples.tsx 中有定义

async load() {
	const [css, js, html] = await Promise.all([
		files(path + "/sample.css"),
		files(path + "/sample.js"),
		files(path + "/sample.html"),
	]);
	return {
		css: css.default,
		html: html.default,
		js: js.default,
	};
},

所以执行 load() 方法就可以获取对应的 html 、css 、js 内容

那么一定有一个地方引用了这个方法,我们全局搜索一下,发现是在website/src/website/pages/playground/PlaygroundPageContent.tsx 里面

 <Select<PlaygroundExample>
  values={getPlaygroundExamples().map(
        (e) => ({
            groupTitle:
            e.chapterTitle,
            items: e.examples,
        })
    )}
    value={ref(
        model,
        "selectedExample"
    )}
    getLabel={(i) =>
        i.title
    }
/>

看来返回值是切换示例的下拉框的数据
在这里插入图片描述
大胆猜测一下, 切换下拉框选项的时候,就会执行对应对象的 load() 方法,加载对应的 html、css、js 的内容,展示在页面上

如下代码控制了 value 的绑定,value 绑定了 model 对象上的 selectedExample,当 value 变化的时候,就会触发 selectedExampleset 方法设置它的值

value={ref(
        model,
        "selectedExample"
    )}

浅浅看一下 ref 的定义

export function ref<T, TProp extends keyof T>(
	obj: T,
	prop: TProp
): IReference<T[TProp]> {
	return {
		get: () => obj[prop],
		set: (value) => (obj[prop] = value),
	};
}

然后我们全局搜一下 selectedExample 对象的定义
website/src/website/pages/playground/PlaygroundModel.ts 方法里面找到了这个对象的 set 方法,即 value 变化的时候会执行的方法

public set selectedExample(value: PlaygroundExample | undefined) {
	this._selectedExample = value;
	this.selectedExampleProject = undefined;
	if (value) {
		value.load().then((p) => {
			runInAction("update example", () => {
				this.selectedExampleProject = {
					example: value,
					project: p,
				};
				this.reloadKey++;
				this.setState(p);
			});
		});
	}
}

这里终于看到了 load() 方法的执行,可以输出一下 p,终于看到这几个内容了
在这里插入图片描述
setState() 方法就是赋值操作

public setState(state: IPlaygroundProject) {
	this.html = state.html;
	this.js = state.js;
	this.css = state.css;
}

当前是 PlaygroundModel 类, PlaygroundPageContent 类里面,在创建的时候,传递了 props 参数

export class PlaygroundPageContent extends React.Component<
    { model: PlaygroundModel },
    {}
> {}

PlaygroundPageContent 类的内部,可以通过 this.props.model 访问 PlaygroundModel
html、css、js 文件的编辑区域是自定义组件 Editor

 <Editor
    language={"javascript"}
    value={ref(model, "js")}
/>

model.js 的值赋给 Editor 类中的 value 属性
进入到 Editor 类的内部
website/src/website/pages/playground/PlaygroundPageContent.tsx

private readonly model = getLoadedMonaco().editor.createModel(
    this.props.value.get(),
    this.props.language
);
render() {
    return (
        <MonacoEditor
            model={this.model}
            onEditorLoaded={(editor) => this.initializeEditor(editor)}
            height={this.props.height}
            className="editor-container"
        />
    );
}

最后返回的是 MonacoEditor 自定义标签,我们在往这个类里瞅一瞅 MonacoEditor
注意这个文件里面定义了好几个类,别找错了哟
website/src/website/components/monaco/MonacoEditor.tsx

private readonly divRef = React.createRef<HTMLDivElement>();
render() {
	const height = "100%";
	return (
		<div
			style={{
				height,
				minHeight: 0,
				minWidth: 0,
			}}
			className="monaco-editor-react"
			ref={this.divRef}
		/>
	);
}

上面代码使用 ref 将变量 divRef 和元素 div 绑定在一起,在 componentDidMount 生命周期钩子函数中,有对这个数据的操作,即以当前的div为容器创建 Monaco Editor 编辑器实例

componentDidMount() {
	// 获取 dom 元素
	const div = this.divRef.current;
	if (!div) {
		throw new Error("unexpected");
	}
	this.resizeObserver.observe(div);
	this.editor = getLoadedMonaco().editor.create(div, {
		model: this.props.model,
		scrollBeyondLastLine: false,
		minimap: { enabled: false },
		automaticLayout: false,
		theme: this.props.theme,
		readOnly: this.props.readOnly,
	});
	this.editor.onDidContentSizeChange((e) => {
		this.setState({ contentHeight: e.contentHeight });
	});
	if (this.props.onEditorLoaded) {
		this.props.onEditorLoaded(this.editor);
	}
}

通过在浏览器中打断点,我发现渲染 html、css、js 的步骤是在 this.props.onEditorLoaded() 方法中实现的,这个方法是通过属性传递过来的,上面的代码里也有,可以全局搜一下

<MonacoEditor
    model={this.model}
    onEditorLoaded={(editor) => this.initializeEditor(editor)}
    height={this.props.height}
    className="editor-container"
/>

那么此时就是要执行 this.initializeEditor(editor) 方法,进行编辑器中内容的初始化。

 initializeEditor(editor: monaco.editor.IStandaloneCodeEditor) {
    this.editor = editor;
    this.disposables.push(this.editor);
    this.disposables.push(
        this.editor.onDidChangeModelContent((e) => {
            this.ignoreChange = true;
            try {
                this.props.value.set(this.editor!.getValue());
            } finally {
                this.ignoreChange = false;
            }
        })
    );
}

最主要的一句代码就是 this.props.value.set(this.editor!.getValue()); ,例如在这里写一个测试数据,页面上就会渲染测试数据:
this.props.value.set(this.editor.getValue());
在这里插入图片描述
至于 value 又是怎么渲染的,原谅此时的我实在是找不到啦!那么就往下继续看吧,不要让暂时的困难困住我们前进的脚步

2、右侧展示效果

右侧的效果就是左侧代码运行之后的结果,我们来验证一下,比如说在 html 里面,增加 Hello 文本,它就会直接出现在右侧

在这里插入图片描述
ok,到这里我发现不学react已经进行不下去了,所以我去react官网逛了一圈,React 官网提供了线上运行的案例,使用的是 codesandbox 线上代码编辑器,然而我发现这个网站用的也是我们的 Monaco !怪不得体验感这么好。不得不说 React 官网的教程真的清晰易懂,生动形象!但是翻译泰拉垮啦🙀 不如看英文
在这里插入图片描述
一万年以后~~~~ React 入门案例学成归来,继续学习 Monaco
接下来我们还是,首先要找到代码在哪里呀,通过检查模式可以看出,右侧部分是一个 iframe,并且其父级类名是 preview

在这里插入图片描述

就可以使用类名full-iframe进行全局搜索,父级类名还得是 preview,发现了它的踪迹就在 /src/website/pages/playground/Preview.tsx 文件里

<iframe
	className="full-iframe"
	key={this.counter}
	sandbox="allow-scripts allow-modals"
	frameBorder={0}
	ref={this.handleIframe}
	src="./playgroundRunner.html"
/>

这个 iframe 的 src 的指向,就是它的具体内容。但是很奇怪,根据指定的目录,找不到 ./playgroundRunner.html 文件,再用这个文件名全局搜索一下子,发现奥秘原来在 webpack.config.ts 里面

module.exports = {
	entry: {
        index: r("src/website/index.tsx"),
        playgroundRunner: r("src/runner/index.ts"),
        monacoLoader: r("src/website/monaco-loader-chunk.ts"),
    },
    plugins: [
    	 new HtmlWebpackPlugin({
            chunks: ["playgroundRunner"],
            filename: "playgroundRunner.html",
            templateContent: getHtml(),
        }),
    ]
}

介个 html 文件的内容,首先是由 templateContent 属性指定的模版确定了一部分。那么就让我们来揭开,这个属性对应的值 getHtml() 方法的真面目吧!其实非常非常的简单,就在 webpack.config.ts 的下面定义的,就是返回了一个基础的 Document 文档结构

function getHtml(): string {
    return `
<!DOCTYPE html>
<html>
	<head>
	<meta charset="utf-8">
	<title>Monaco Editor</title>
	</head>
	<body>
	</body>
</html>`;
}

这个模版只是给出了html内容的模版,具体的内容还是要看 playgroundRunner chunk 的入口文件执行的代码是怎么样的。它的入口文件就是src/runner/index.ts,里面的内容不得不说,还挺复杂。另外,要注意当前 chunk 生成的 html 文件是通过 iframe 的方式使用的哦!
这个入口文件做的事情,大致来说,就是给 iframe 增加了一个事件监听,用来监听父级 window 发送过来的数据,如果父级 window 发布了要该 iframe 初始化的命令,它就会乖乖的执行初始化的方法
父级传递给这个 iframe 的东东,其实就是左侧编辑区域的代码,咱们可以在初始化的方法里面打印一下这个方法接收到的参数
在这里插入图片描述
在这里插入图片描述
在初始化方法里面,会把参数中的 css、html 都加到当前 iframe 里面,并且会通过 eval() 方法,执行 js 里面的代码,执行完 js 里面的代码,右侧就会创建出来一个 Monaco Editor 编辑器实例啦,并且里面的内容是我们可以通过左侧编辑器修改的。

四、总结

本篇从 Monaco Editor 下载、启动开始,顺着入口文件以及示例 Hello World 项目的脉络,学习了源码的执行过程。学习这种大型项目的源码一定要有耐心,另外就是在学习的过程中需要查漏补缺,遇到不会的东西补一补,慢慢的积累。

参考文章:
1、https://juejin.cn/post/6844903889393680392
2、html-webpack-plugin
3、require.context()的用法详解

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

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

相关文章

FreeCAD傻瓜教程之创建参数化几何图形-螺旋体、平面、球体、椭球体、圆柱体、圆锥体、棱柱、椭圆

目的&#xff1a;学会用FreeCAD绘制参数化的几何图形。 一、使用的工作台和工具 1.1选择Part 工作台 1.2单击创建图元...工具 也就是上图黄色工具区域的倒数第2个 1.3 打开几何图元 下方的下拉列表 二、绘制螺旋体、弹簧、螺丝杆 2.1 选择几何图元列表中的 “螺旋体” 设…

opengl日记10-opengl使用多个纹理示例

文章目录 环境代码CMakeLists.txt文件内容不变。fragmentShaderSource.fsvertexShaderSource.vsmain.cpp 总结 环境 系统&#xff1a;ubuntu20.04opengl版本&#xff1a;4.6glfw版本&#xff1a;3.3glad版本&#xff1a;4.6cmake版本&#xff1a;3.16.3gcc版本&#xff1a;10.…

常见分布式ID解决方案

简介&#xff1a; 分布式ID解决方案是用于在分布式系统中生成唯一标识符的方案。常见的分布式ID解决方案可总结为3点&#xff1a;数据库方案、算法方案、开源组件方案。 分布式ID 分布式 ID&#xff08;Distributed ID&#xff09;是指在分布式系统中生成全局唯一的标识符&…

10000字!一文学会SQL数据分析

文章来源于山有木兮 原文链接&#xff1a;https://edu.cda.cn/goods/show/3412?targetId5695&preview0 第1节 SQL简介与基础知识 做数据分析的&#xff0c;为什么要写SQL&#xff1f; 没有数据的情况下&#xff0c;我们分析数据就像是巧妇难为无米之炊。因此&#xff0c…

【prometheus-operator】k8s监控redis

1、准备exporter https://github.com/oliver006/redis_exporter oliver006-redis_exporter-amd64.tar # 安装镜像 docker load -i oliver006-redis_exporter-amd64.tar # 上传镜像 docker tag oliver006/redis_exporter ip/monitor/redis_exporter:latest docker push ip/mo…

零基础入门数据挖掘系列之「建模调参」

摘要&#xff1a;对于数据挖掘项目&#xff0c;本文将学习如何建模调参&#xff1f;从简单的模型开始&#xff0c;如何去建立一个模型&#xff1b;如何进行交叉验证&#xff1b;如何调节参数优化等。 建模调参&#xff1a;特征工程也好&#xff0c;数据清洗也罢&#xff0c;都是…

强大的文本编辑器:Sublime Text for Mac注册激活版

Sublime Text for Mac是一款功能强大的文本编辑器&#xff0c;特别适合程序员和开发者使用。它提供了丰富的功能&#xff0c;如智能代码补全、语法高亮、自定义快捷键、项目管理、多行选择、自动保存等&#xff0c;以提高代码编写效率和舒适度。此外&#xff0c;Sublime Text还…

【鸿蒙HarmonyOS开发笔记】通知模块之发布基础类型通知,内含如何将图片变成PixelMap对象

通知简介 应用可以通过通知接口发送通知消息&#xff0c;终端用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用。 通知常见的使用场景&#xff1a; 显示接收到的短消息、即时消息等。 显示应用的推送消息&#xff0c;如广告、版本更新等。 显示当前正…

数字功放VS模拟功放,选择适合你的音频解决方案

数字功放和模拟功放是音频系统中常用的两种功放技术&#xff0c;适用于不同的音频应用&#xff0c;都具有各自的优势和特点。本文将为您详细介绍数字功放和模拟功放的差异&#xff0c;并帮助您找到适合自己的音频解决方案。 1、数字功放是一种利用数字信号处理技术的功放。它将…

Qt 坐标位置转换

Qt 坐标位置转换 文章目录 Qt 坐标位置转换常见的位置坐标转换Qt窗体中常用坐标的区别与获取途径当前光标相对于屏幕的绝对位置当前光标相对于当前窗口的位置鼠标事件发生的位置窗体的位置判断鼠标光标是否悬浮在某个子控件上 从事Qt快一年了 &#xff0c;在做坐标转换的时候容…

钡铼技术R40工业4G路由器加速推进农田水利设施智能化

钡铼技术R40工业4G路由器作为一种先进的通信设备&#xff0c;正在被广泛应用于各行各业&#xff0c;其中包括农田水利设施的智能化改造。通过结合钡铼技术R40工业4G路由器&#xff0c;农田水利设施可以实现更高效的管理和运营&#xff0c;提升农田灌溉、排水等工作效率&#xf…

OpenAI的GPT已达极限,更看好AI Agent

日前&#xff0c;比尔盖茨发表文章表示&#xff1a;AI Agent不仅会改变人与电脑的互动方式&#xff0c;或许还将颠覆软件行业&#xff0c;引领自输入命令到点击图标以来的最大计算机革命。 在数字化和技术创新的浪潮中&#xff0c;AI Agent作为一种前沿技术&#xff0c;正开启…

基于 HBase Phoenix 构建实时数仓(5)—— 用 Kafka Connect 做实时数据同步

目录 一、总体架构 二、安装配置 MySQL 1. 创建 mysql 用户 2. 建立 MySQL 使用的目录 3. 解压安装包 4. 配置环境变量 5. 创建 MySQL 配置文件 6. MySQL 系统初始化 7. 启动 mysql 服务器 8. 创建 dba 用户 三、配置 MySQL 主从复制 四、安装部署 Kafka Connector…

Docker常用命令!!!

一、docker基础命令 1、启动docker systemctl start docker 2、关闭docker systemctl stop docker 3、重启docker systemctl restart docker 4、docker设置随服务启动而自启动 systemctl enable docker 5、查看docker 运行状态 systemctl status docker 6、查看docker 版本号信…

ChatGPT在大气科学领域建模、数据分析、可视化与资源评估中的高效应用及论文写作

深度探讨人工智能在大气科学中的应用&#xff0c;特别是如何结合最新AI模型与Python技术处理和分析气候数据。课程介绍包括GPT-4等先进AI工具&#xff0c;旨在帮助学员掌握这些工具的功能及应用范围。课程内容覆盖使用GPT处理数据、生成论文摘要、文献综述、技术方法分析等实战…

Learn OpenGL 19 几何着色器

几何着色器 在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader)&#xff0c;几何着色器的输入是一个图元&#xff08;如点或三角形&#xff09;的一组顶点。几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换。然而&#xff0c;几何着色器最有趣的地方…

IOS/Android App备案(uniapp)

IOS/App备案 IOS备案Android备案 IOS备案 准备好p12证书即可 链接: https://aitoolnav.caichuangkeji.com/#/AppMd5 Android备案 上DCLOUD开发者中心&#xff0c;找到相关应用后&#xff0c;直接查看证书即可获取到MD5 公钥&#xff1a;先根据上述页面下载证书&#xff0c;…

应急响应-Web2

应急响应-Web2 1.攻击者的IP地址&#xff08;两个&#xff09;&#xff1f; 192.168.126.135 192.168.126.129 通过phpstudy查看日志&#xff0c;发现192.168.126.135这个IP一直在404访问 &#xff0c; 并且在日志的最后几条一直在访问system.php &#xff0c;从这可以推断 …

Git原理与使用(一)

目录 前言 版本控制器 Linux下的Git的安装 Git的基本操作 创建Git本地仓库 配置Git 工作区、暂存区、版本库 添加与提交 查看.git文件 前言 我们可能要写多个文档对一个产品进行描述&#xff0c;但是一般情况下我们可能要写多个文档&#xff0c;比如&#xff1a; 初…

Rust Rocket简单入门

简介 Rust中最知名的两个web框架要数Rocket和Actix了&#xff0c;Rocket更注重易用性&#xff0c;Actix则更注重性能。这里只是了解一下Rust下的WebAPI开发流程&#xff0c;就学一下最简单的 Rocket。 Rocket 是一个用于 Rust 的异步 Web 框架&#xff0c;专注于可用性、安全性…