字玩FontPlayer开发笔记8 Tauri2文件系统

news2025/1/7 22:23:54

字玩FontPlayer开发笔记8 Tauri2文件系统

字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 + ElementUI开发,源代码:
github: https://github.com/HiToysMaker/fontplayer
gitee: https://gitee.com/toysmaker/fontplayer

笔记

字玩目前是用Electron进行桌面端应用打包,但是性能体验不太好,一直想替换成Tauri。Tauri的功能和Electron类似,都可以把前端代码打包生成桌面端(比如Windows和Mac)应用。Tauri只使用系统提供的WebView,不像Electron一样内置Chromium和Node.js,性能体验更佳。

近几天开始着手将Electron替换成Tauri,前两天完成了系统原生菜单的基本设置,今天将菜单功能实装。笔者项目中菜单功能最常用的就是文件存储和读取,所以今天主要学习了文件系统的内容。Tauri2提供了js端可调用的plugin,可以方便前端轻松实现文件操作。

权限配置

文件操作需要进行权限配置,Tauri2去掉了tauri.conf.json中的allowList一项,变成在src-tauri/capabilities/default.json中进行权限设置。
具体权限对应的选项在文档中Permission Table一栏中有详述:https://tauri.app/plugin/file-system/

笔者的配置:
src-tauri/capabilities/default.json

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "enables the default permissions",
  "windows": [
    "main"
  ],
  "permissions": [
    {
      "identifier": "fs:scope",
      "allow": [
        {
          "path": "$APPDATA"
        },
        {
          "path": "$APPDATA/**"
        }
      ]
    },
    "core:default",
    "fs:read-files",
    "fs:write-files",
    "fs:allow-appdata-read-recursive",
    "fs:allow-appdata-write-recursive",
    "fs:default",
    "dialog:default"
  ]
}
文件选择对话框

文件选择对话框的插件和文件操作的插件是分开的,首先安装对话框插件:

npm run tauri add dialog

打开文件选择窗口:

import { open } from '@tauri-apps/plugin-dialog'

const file = await open({
  multiple: false,
  directory: false,
})

打开文件存储窗口:

import { save } from '@tauri-apps/plugin-dialog'

const path = await save({
  defaultPath: 'untitled',
  filters: [
    {
      name: 'My Filter',
      extensions: ['png', 'jpeg'],
    },
  ],
});
文件操作

文件操作封装在fs插件中,插件分别提供对纯文本读写和二进制读写的方法。

安装插件:

npm run tauri add fs

读取纯文本:

import { readTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'

const configToml = await readTextFile('config.toml', {
  baseDir: BaseDirectory.AppConfig,
})

读取二进制文本:

const icon = await readFile('icon.png', {
  baseDir: BaseDirectory.Resources,
})

写入纯文本:

import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'

const contents = JSON.stringify({ notifications: true });
await writeTextFile('config.json', contents, {
  baseDir: BaseDirectory.AppConfig,
});

写入二进制:

import { writeFile, BaseDirectory } from '@tauri-apps/plugin-fs'

const contents = new Uint8Array();
await writeFile('config', contents, {
  baseDir: BaseDirectory.AppConfig,
});
涉及文件操作的具体逻辑实现
保存文本文件
const nativeSaveText = async (data, filename, formats) => {
	const path = await save({
		defaultPath: filename,
		filters: [
			{
				name: 'Filter',
				extensions: formats,
			},
		],
	})
	if (path) {
		await writeTextFile(path, data)
	}
}
保存二进制文件
const nativeSaveBinary = async (data, filename, formats) => {
	const path = await save({
		defaultPath: filename,
		filters: [
			{
				name: 'Filter',
				extensions: formats,
			},
		],
	})
	if (path) {
		await writeFile(path, data)
	}
}
打开文本文件
const nativeImportTextFile = async (formats) => {
	const path = await open({
		filters: [
			{
				name: 'Filter',
				extensions: formats,
			},
		],
	})
	let data = null
	let name = 'untitled'
	if (path) {
		data = await readTextFile(path)
		name = path.split('/').pop().split('.')[0]
	}
	return {
		data,
		name,
	}
}
打开二进制文件
const nativeImportFile = async (formats) => {
	const path = await open({
		filters: [
			{
				name: 'Filter',
				extensions: formats,
			},
		],
	})
	let uint8Array = null
	let name = 'untitled'
	if (path) {
		uint8Array = await readFile(path)
		name = path.split('/').pop().split('.')[0]
	}
	return {
		uint8Array,
		name,
	}
}
打开工程
const openFile_tauri = async (rawdata) => {
	if (files.value && files.value.length) {
		tips.value = '目前字玩仅支持同时编辑一个工程,请关闭当前工程再打开新工程。注意,关闭工程前请保存工程以避免数据丢失。'
		tipsDialogVisible.value = true
	} else {
		const { data } = await nativeImportTextFile(['json'])
		await _openFile_electron(data)
	}
}
保存工程
const saveFile_tauri = async () => {
	setSaveDialogVisible(true)
}
另存为工程
const saveAs_tauri = async () => {
	setSaveDialogVisible(true)
}
导入字体库
const importFont_tauri = async () => {
	if (files.value && files.value.length) {
		tips.value = '目前字玩仅支持同时编辑一个工程,请关闭当前工程再导入字体。注意,关闭工程前请保存工程以避免数据丢失。'
		tipsDialogVisible.value = true
	} else {
		const options = await nativeImportFile(['otf', 'ttf'])
		await _importFont_tauri(options)
	}
}
导入字形
const importGlyphs_tauri = async () => {
	const { data: rawdata } = await nativeImportTextFile(['json'])
	if (!rawdata) return
	const data = JSON.parse(rawdata)
	const plainGlyphs = data.glyphs
	if (data.constants) {
		for (let n = 0; n < data.constants.length; n++) {
			if (!constantsMap.getByUUID(data.constants[n].uuid)) {
				constants.value.push(data.constants[n])
			}
		}
	}
	if (data.constantGlyphMap) {
		const keys = Object.keys(data.constantGlyphMap)
		for (let n = 0; n < keys.length; n++) {
			constantGlyphMap.set(keys[n], data.constantGlyphMap[keys[n]])
		}
	}
	const _glyphs = plainGlyphs.map((plainGlyph) => instanceGlyph(plainGlyph))
	_glyphs.map((glyph) => {
		addGlyph(glyph, editStatus.value)
		addGlyphTemplate(glyph, editStatus.value)
	})
	if (editStatus.value === Status.GlyphList) {
		emitter.emit('renderGlyphPreviewCanvas')
	} else if (editStatus.value === Status.StrokeGlyphList) {
		emitter.emit('renderStrokeGlyphPreviewCanvas')
	} else if (editStatus.value === Status.RadicalGlyphList) {
		emitter.emit('renderRadicalGlyphPreviewCanvas')
	} else if (editStatus.value === Status.CompGlyphList) {
		emitter.emit('renderCompGlyphPreviewCanvas')
	}
}
导入SVG
const importSVG_tauri = async () => {
	const { data: rawdata } = await nativeImportTextFile(['svg'])
	if (!rawdata) return
	const svgEl: HTMLElement = parseStrToSvg(rawdata).childNodes[0] as HTMLElement
	const components = parseSvgToComponents(svgEl as HTMLElement)
	components.forEach((component: IComponent) => {
		addComponentForCurrentCharacterFile(component)
	})
}
识别图片
const importPic_tauri = async () => {
	const options = await nativeImportFile(['jpg', 'png', 'jpeg'])
	const { name, uint8Array } = options
	let binary = ''
  uint8Array.forEach((byte) => {
    binary += String.fromCharCode(byte);
  })
  const base64str = btoa(binary)
	const type = name.split('.')[1] === 'png' ? 'imge/png' : 'image/jpeg'
	const dataUrl = `data:${type};base64,${base64str}`
	total.value = 0
	loaded.value = 0
	loading.value = true
	const img = document.createElement('img')
	img.onload = () => {
		setTimeout(() => {
			thumbnail(dataUrl, img, 1000)
			setEditStatus(Status.Pic)
			loading.value = false
		}, 100)
	}
	img.src = dataUrl
}
导出字体库
const exportFont_tauri = async (options: CreateFontOptions) => {
	const font = createFont(options)
	const buffer = toArrayBuffer(font) as ArrayBuffer
	const filename = `${selectedFile.value.name}.otf`
	nativeSaveBinary(buffer, filename, ['otf'])
}
导出字形
const exportGlyphs_tauri = async () => {
	if (editStatus.value === Status.GlyphList) {
		const _glyphs = glyphs.value.map((glyph: ICustomGlyph) => {
			return plainGlyph(glyph)
		})
		const data = JSON.stringify({
			glyphs: _glyphs,
			constants: constants.value,
			constantGlyphMap: mapToObject(constantGlyphMap),
			version: 1.0,
		})
		await nativeSaveText(data, `glyphs.json`, ['json'])
	} else if (editStatus.value === Status.StrokeGlyphList) {
		const _glyphs = stroke_glyphs.value.map((glyph: ICustomGlyph) => {
			return plainGlyph(glyph)
		})
		const data = JSON.stringify({
			glyphs: _glyphs,
			constants: constants.value,
			constantGlyphMap: mapToObject(constantGlyphMap),
			version: 1.0,
		})
		await nativeSaveText(data, `stroke_glyphs.json`, ['json'])
	} else if (editStatus.value === Status.RadicalGlyphList) {
		const _glyphs = radical_glyphs.value.map((glyph: ICustomGlyph) => {
			return plainGlyph(glyph)
		})
		const data = JSON.stringify({
			glyphs: _glyphs,
			constants: constants.value,
			constantGlyphMap: mapToObject(constantGlyphMap),
			version: 1.0,
		})
		await nativeSaveText(data, `radical_glyphs.json`, ['json'])
	} else if (editStatus.value === Status.CompGlyphList) {
		const _glyphs = comp_glyphs.value.map((glyph: ICustomGlyph) => {
			return plainGlyph(glyph)
		})
		const data = JSON.stringify({
			glyphs: _glyphs,
			constants: constants.value,
			constantGlyphMap: mapToObject(constantGlyphMap),
			version: 1.0,
		})
		nativeSaveText(data, `comp_glyphs.json`, ['json'])
	} else {
		const _glyphs = glyphs.value.map((glyph: ICustomGlyph) => {
			return plainGlyph(glyph)
		})
		const data = JSON.stringify({
			glyphs: _glyphs,
			constants: constants.value,
			constantGlyphMap: mapToObject(constantGlyphMap),
			version: 1.0,
		})
		await nativeSaveText(data, `glyphs.json`, ['json'])
	}
}
导出JPEG图片
const exportJPEG_tauri = async () => {
	// 导出JPEG
	const _canvas = canvas.value as HTMLCanvasElement
	const data = _canvas.toDataURL('image/jpeg')
	const buffer = base64ToArrayBuffer(data)
	const fileName = `${editCharacterFile.value.character.text}.jpg`
	nativeSaveBinary(buffer, fileName, ['jpg', 'jpeg'])
}
导出PNG图片
const exportPNG_tauri = async () => {
	// 导出PNG
	const _canvas = canvas.value as HTMLCanvasElement
	render(_canvas, false)
	const data = _canvas.toDataURL('image/png')
	const buffer = base64ToArrayBuffer(data)
	const fileName = `${editCharacterFile.value.character.text}.png`
	nativeSaveBinary(buffer, fileName, ['png'])
	render(_canvas, true)
}
导出SVG
const exportSVG_tauri = async () => {
	// 导出SVG
	if (editStatus.value !== Status.Edit && editStatus.value !== Status.Glyph ) return
	const components = editStatus.value === Status.Edit ? orderedListWithItemsForCurrentCharacterFile.value : orderedListWithItemsForCurrentGlyph.value
	const data = componentsToSvg(components, selectedFile.value.width, selectedFile.value.height)
	const fileName = `${editCharacterFile.value.character.text}.svg`
	nativeSaveText(data, fileName, ['svg'])
}
前端与Rust端通信

实现点击菜单按钮事件需要前后端通信,菜单由Rust生成并监听事件,但具体事件逻辑由前端实现。
Rust端代码,声明每个菜单按钮的事件函数,函数中逻辑比较简单,就是发送相应消息给前端,让前端知道目前要做的操作,具体逻辑在前端实现。

#[tauri::command]
fn create_file(app: AppHandle) {
  app.emit("create-file", ()).unwrap();
}

#[tauri::command]
fn open_file(app: AppHandle) {
  app.emit("open-file", ()).unwrap();
}

#[tauri::command]
fn save_file(app: AppHandle) {
  app.emit("save-file", ()).unwrap();
}

#[tauri::command]
fn save_as(app: AppHandle) {
  app.emit("save-as", ()).unwrap();
}

#[tauri::command]
fn undo(app: AppHandle) {
  app.emit("undo", ()).unwrap();
}

#[tauri::command]
fn redo(app: AppHandle) {
  app.emit("redo", ()).unwrap();
}

#[tauri::command]
fn cut(app: AppHandle) {
  app.emit("cut", ()).unwrap();
}

#[tauri::command]
fn copy(app: AppHandle) {
  app.emit("copy", ()).unwrap();
}

#[tauri::command]
fn paste(app: AppHandle) {
  app.emit("paste", ()).unwrap();
}

#[tauri::command]
fn del(app: AppHandle) {
  app.emit("delete", ()).unwrap();
}

#[tauri::command]
fn import_font_file(app: AppHandle) {
  app.emit("import-font-file", ()).unwrap();
}

#[tauri::command]
fn import_templates_file(app: AppHandle) {
  app.emit("import-templates-file", ()).unwrap();
}

#[tauri::command]
fn import_glyphs(app: AppHandle) {
  app.emit("import-glyphs", ()).unwrap();
}

#[tauri::command]
fn import_pic(app: AppHandle) {
  app.emit("import-pic", ()).unwrap();
}

#[tauri::command]
fn import_svg(app: AppHandle) {
  app.emit("import-svg", ()).unwrap();
}

#[tauri::command]
fn export_font_file(app: AppHandle) {
  app.emit("export-font-file", ()).unwrap();
}

#[tauri::command]
fn export_glyphs(app: AppHandle) {
  app.emit("export-glyphs", ()).unwrap();
}

#[tauri::command]
fn export_jpeg(app: AppHandle) {
  app.emit("export-jpeg", ()).unwrap();
}

#[tauri::command]
fn export_png(app: AppHandle) {
  app.emit("export-png", ()).unwrap();
}

#[tauri::command]
fn export_svg(app: AppHandle) {
  app.emit("export-svg", ()).unwrap();
}

#[tauri::command]
fn add_character(app: AppHandle) {
  app.emit("add-character", ()).unwrap();
}

#[tauri::command]
fn add_icon(app: AppHandle) {
  app.emit("add-icon", ()).unwrap();
}

#[tauri::command]
fn font_settings(app: AppHandle) {
  app.emit("font-settings", ()).unwrap();
}

#[tauri::command]
fn preference_settings(app: AppHandle) {
  app.emit("preference-settings", ()).unwrap();
}

#[tauri::command]
fn language_settings(app: AppHandle) {
  app.emit("language-settings", ()).unwrap();
}

#[tauri::command]
fn import_template1(app: AppHandle) {
  app.emit("template-1", ()).unwrap();
}

#[tauri::command]
fn remove_overlap(app: AppHandle) {
  app.emit("remove_overlap", ()).unwrap();
}

Rust端代码,监听事件:

app.on_menu_event(move |app, event| {
  if event.id() == "create-file" {
    create_file(app.app_handle().clone())
  } else if event.id() == "open-file" {
    open_file(app.app_handle().clone())
  } else if event.id() == "save-file" {
    save_file(app.app_handle().clone())
  } else if event.id() == "save-as" {
    save_as(app.app_handle().clone())
  } else if event.id() == "undo" {
    undo(app.app_handle().clone())
  } else if event.id() == "redo" {
    redo(app.app_handle().clone())
  } else if event.id() == "cut" {
    cut(app.app_handle().clone())
  } else if event.id() == "copy" {
    copy(app.app_handle().clone())
  } else if event.id() == "paste" {
    paste(app.app_handle().clone())
  } else if event.id() == "delete" {
    del(app.app_handle().clone())
  } else if event.id() == "import-font-file" {
    import_font_file(app.app_handle().clone())
  } else if event.id() == "import-templates-file" {
    import_templates_file(app.app_handle().clone())
  } else if event.id() == "import-glyphs" {
    import_glyphs(app.app_handle().clone())
  } else if event.id() == "import-pic" {
    import_pic(app.app_handle().clone())
  } else if event.id() == "import-svg" {
    import_svg(app.app_handle().clone())
  } else if event.id() == "export-font-file" {
    export_font_file(app.app_handle().clone())
  } else if event.id() == "export-glyphs" {
    export_glyphs(app.app_handle().clone())
  } else if event.id() == "export-jpeg" {
    export_jpeg(app.app_handle().clone())
  } else if event.id() == "export-png" {
    export_png(app.app_handle().clone())
  } else if event.id() == "export-svg" {
    export_svg(app.app_handle().clone())
  } else if event.id() == "add-character" {
    add_character(app.app_handle().clone())
  } else if event.id() == "add-icon" {
    add_icon(app.app_handle().clone())
  } else if event.id() == "font-settings" {
    font_settings(app.app_handle().clone())
  } else if event.id() == "preference-settings" {
    preference_settings(app.app_handle().clone())
  } else if event.id() == "language-settings" {
    language_settings(app.app_handle().clone())
  } else if event.id() == "template-1" {
    import_template1(app.app_handle().clone())
  } else if event.id() == "remove_overlap" {
    remove_overlap(app.app_handle().clone())
  }        
});

前端代码,监听消息:

const initTauri = () => {
	const keys = Object.keys(tauri_handlers)
	for (let i = 0; i < keys.length; i++) {
		const key = keys[i]
		listen(key, (event) => {
			tauri_handlers[key]()
		})
	}
}

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

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

相关文章

SwiftUI 撸码常见错误 2 例漫谈

概述 在 SwiftUI 日常撸码过程中&#xff0c;头发尚且还算茂盛的小码农们经常会犯这样那样的错误。虽然犯这些错的原因都很简单&#xff0c;但有时想要快速准确的定位它们却并不容易。 况且这些错误还可能在模拟器和 Xcode 预览&#xff08;Preview&#xff09;表现的行为不甚…

linux系统(ubuntu,uos等)连接鸿蒙next(mate60)设备

以前在linux上是用adb连接&#xff0c;现在升级 到了鸿蒙next&#xff0c;adb就不好用了。得用Hdc来了&#xff0c;在windows上安装了hisuit用的好好的&#xff0c;但是到了linux(ubuntu2204)下载安装了 下载中心 | 华为开发者联盟-HarmonyOS开发者官网&#xff0c;共建鸿蒙生…

2024年12月HarmonyOS应用开发者高级认证全新题库

注意事项&#xff1a;切记在考试之外的设备上打开题库进行搜索&#xff0c;防止切屏三次考试自动结束&#xff0c;题目是乱序&#xff0c;每次考试&#xff0c;选项的顺序都不同&#xff0c;作者已于2024年12月15日又更新了一波题库&#xff0c;题库正确率99%&#xff01; 新版…

Anaconda安装(2024最新版)

安装新的anaconda需要卸载干净上一个版本的anaconda&#xff0c;不然可能会在新版本安装过程或者后续使用过程中出错&#xff0c;完全卸载干净anaconda的方法&#xff0c;可以参考我的博客&#xff01; 第一步&#xff1a;下载anaconda安装包 官网&#xff1a;Anaconda | The O…

docker中使用Volume完成数据共享

情景概述 在一个docker中&#xff0c;部署两个MySQL容器&#xff0c;假如它们的数据都存储在自己容器内部的data目录中。这样的存储方式会有以下问题&#xff1a; 1.无法保证两个MySQL容器中的数据同步。 2.容器删除后&#xff0c;数据就会丢失。 基于以上问题&#xff0c;容…

Mac软件介绍之录屏软件Filmage Screen

软件介绍 Filmage Screen 是一款专业的视频录制和编辑软件&#xff0c;适用于 Mac 系统 可以选择4k 60fps&#xff0c;可以选择录制电脑屏幕&#xff0c;摄像头录制&#xff0c;可以选择区域录制。同时也支持&#xff0c;简单的视频剪辑。 可以同时录制电脑麦克风声音 标准…

【Redis经典面试题七】Redis的事务机制是怎样的?

目录 一、Redis的事务机制 二、什么是Redis的Pipeline&#xff1f;和事务有什么区别&#xff1f; 三、Redis的事务和Lua之间有哪些区别&#xff1f; 3.1 原子性保证 3.2 交互次数 3.3 前后依赖 3.4 流程编排 四、为什么Lua脚本可以保证原子性&#xff1f; 五、为什么R…

【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1. 相关排序和查找算法的原理 2. C 类与成员函数的定义 3. 数组作为类的成员变量的处理 4. 函数参数传递与返回值处理 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a; 将直接插入排序、直接选择排序、冒泡…

[开源]自动化定位建图系统

系统状态机&#xff1a; 效果展示&#xff1a; 1、 机器人建图定位系统-基础重定位&#xff0c;定位功能演示 2、 机器人建图定位系统-增量地图构建&#xff0c;手动回环检测演示 3、敬请期待… 开源链接&#xff1a; 1、多传感器融合里程计 https://gitee.com/li-wenhao-lw…

简单的jmeter数据请求学习

简单的jmeter数据请求学习 1.需求 我们的流程服务由原来的workflow-server调用wfms进行了优化&#xff0c;将wfms服务操作并入了workflow-server中&#xff0c;去除了原来的webservice服务调用形式&#xff0c;增加了并发处理&#xff0c;现在想测试模拟一下&#xff0c;在一…

conda/pip基本常用命令理解与整理

最近配置了两轮pytorch环境&#xff0c;由于要频繁用到各种conda和pip命令&#xff0c;所以再此整理一下。 文章目录 前言&#xff1a;conda虚拟环境总结与解读Conda和pip的理解区别和联系命令格式 conda环境命令查看创建和删除导出与导入激活和退出 包管理命令安装和删除文件批…

Maven 详细配置:Maven settings 配置文件的详细说明

Maven settings 配置文件是 Maven 环境的重要组成部分&#xff0c;它用于定义用户特定的配置信息和全局设置&#xff0c;例如本地仓库路径、远程仓库镜像、代理服务器以及认证信息等。settings 文件分为全局配置文件&#xff08;settings.xml&#xff09;和用户配置文件&#x…

Unity-Mirror网络框架-从入门到精通之Chat示例

文章目录 前言Chat聊天室Authentication授权ChatAuthenticatorChat示例中的授权流程聊天Chat最后 前言 在现代游戏开发中&#xff0c;网络功能日益成为提升游戏体验的关键组成部分。Mirror是一个用于Unity的开源网络框架&#xff0c;专为多人游戏开发设计。它使得开发者能够轻…

复杂园区网基本分支的构建

目录 1、各主机进行网络配置。2、交换机配置。3、配置路由交换&#xff0c;进行测试。4、配置路由器接口和静态路由&#xff0c;进行测试。5、最后测试任意两台主机通信情况 模拟环境链接 拓扑结构 说明&#xff1a; VLAN标签在上面的一定是GigabitEthernet接口的&#xff0c…

这是什么操作?强制迁移?GitLab 停止中国区用户访问

大家好&#xff0c;我是鸭鸭&#xff01; 全球知名代码托管平台 GitLab 发布通告&#xff0c;宣布不再为位于中国大陆、香港及澳门地区的用户提供访问服务&#xff0c;并且“贴心”建议&#xff0c;可以访问极狐 GitLab。 极狐 GitLab 是一家中外合资公司&#xff0c;宣称获得…

CDP集成Hudi实战-spark shell

[〇]关于本文 本文主要解释spark shell操作Hudi表的案例 软件版本Hudi1.0.0Hadoop Version3.1.1.7.3.1.0-197Hive Version3.1.3000.7.3.1.0-197Spark Version3.4.1.7.3.1.0-197CDP7.3.1 [一]使用Spark-shell 1-配置hudi Jar包 [rootcdp73-1 ~]# for i in $(seq 1 6); do s…

设计模式学习[15]---适配器模式

文章目录 前言1.引例2.适配器模式2.1 对象适配器2.2 类适配器 总结 前言 这个模式其实在日常生活中有点常见&#xff0c;比如我们的手机取消了 3.5 m m 3.5mm 3.5mm的接口&#xff0c;只留下了一个 T y p e − C Type-C Type−C的接口&#xff0c;但是我现在有一个 3.5 m m 3.…

数据挖掘——数据预处理

数据挖掘——数据预处理 数据预处理数据预处理 ——主要任务数据清洗如何处理丢失的数据如何处理噪声数据如何处理不一致数据 数据集成相关分析相关系数(也成为皮尔逊相关系数)协方差 数据规约降维法&#xff1a;PCA主成分分析降数据——抽样法数据压缩 数据预处理 数据预处理…

Unity-Mirror网络框架-从入门到精通之CCU示例

文章目录 前言什么是CCU&#xff1f;测试结果最后 前言 在现代游戏开发中&#xff0c;网络功能日益成为提升游戏体验的关键组成部分。Mirror是一个用于Unity的开源网络框架&#xff0c;专为多人游戏开发设计。它使得开发者能够轻松实现网络连接、数据同步和游戏状态管理。本文…

如何在 Ubuntu 22.04 上安装 Nagios 服务器教程

简介 在本教程中&#xff0c;我们将解释如何在 Ubuntu 22.04 上安装和配置 Nagios&#xff0c;使用 Apache 作为 Web 服务器&#xff0c;并通过 Let’s Encrypt Certbot 使用 SSL 证书进行保护。 Nagios 是一个强大的监控系统&#xff0c;它可以帮助组织在 IT 基础设施问题影…