当前位置: 首页 > news >正文

IntelliJ IDE 插件开发 | (五)VFS 与编辑器

系列文章

  • IntelliJ IDE 插件开发 |(一)快速入门
  • IntelliJ IDE 插件开发 |(二)UI 界面与数据持久化
  • IntelliJ IDE 插件开发 |(三)消息通知与事件监听
  • IntelliJ IDE 插件开发 |(四)来查收你的 IDEA 使用报告吧
  • IntelliJ IDE 插件开发 |(五)VFS 与编辑器

前言

在前几篇文章中主要介绍了关于 IntelliJ IDE 插件开发的基础知识,这部分内容对开发一些小功能的插件的开发已经足够。不过,如果想要开发一些与具体编程语言相关的、提升开发效率的插件(例如MybatisX),那么前几篇的内容就不足以支撑了。而从本篇开始,则会介绍实现相关功能所需要的知识: VFS、编辑器、PSI、自定义语言等,最后再以两三个插件的实战开发(例如老生常谈的代码生成)进行结尾,本文涉及到的完整代码已上传到GitHub。

VFS(Virtual File System)

VFS(虚拟文件系统)可以看作是 IntelliJ 平台对各种类型(本地磁盘的文件、DIFF 信息文件、远程文件)文件操作的封装,通过提供一致的 API 以及文件变更事件,让开发人员可以专注于对文件的处理。此外之所以使用虚拟二字,也是因为我们在对文件操作时并没有直接修改源文件,而是修改了源文件所对应的快照,然后 VFS 会通过同步或者异步的方式去修改源文件。关于 VFS 主要有以下注意点:

  1. 文件快照是应用级别的,因此一个文件即使被多个项目使用也只会对应一份快照。

  2. 文件快照内容和源文件内容并非实时对应,例如文件已从资源管理器中删除,但 IntelliJ 平台只有接收并处理了文件删除事件后才会从快照中删除指定文件。

  3. VFS 通过操作系统的 File Watcher 去感知文件的变化(基于时间戳),可以通过下述步骤去查看被监听的文件根节点:

    image-20240105140441390

    image-20240105140448931

  4. 如果在代码中访问了被忽略的文件(例如下图中配置的),VFS 就会直接加载并返回文件的内容而非是快照的内容:

    image-20240105140934840

  5. 如果需要保证代码是在文件刷新后完成,则可以使用以下方式:

    val virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE)
    virtualFile?.let {it.refresh(true, true) {// 文件刷新完成}
    }
    

    refresh 的方法签名如下,第三个参数 postRunnable 用于传递我们要执行的代码:

    /*** The same as {@link #refresh(boolean, boolean)} but also runs {@code postRunnable}* after the operation is completed. The runnable is executed on event dispatch thread inside write action.*/
    public abstract void refresh(boolean asynchronous, boolean recursive, @Nullable Runnable postRunnable);
    

获取 VirtualFile 对象的方式

以下内容来自官网:

ContextAPI
ActionAnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE) AnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY) for multiple selection
DocumentFileDocumentManager.getFile()
PSI FilePsiFile.getVirtualFile()(may return null if the PSI file exists only in memory)
File NameFilenameIndex.getVirtualFilesByName()
Local File System PathLocalFileSystem.findFileByIoFile() VirtualFileManager.findFileByNioPath()/refreshAndFindFileByNioPath() (2020.2+)

在上面的示例中就是使用的第一种方式:AnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE) 。

VirtualFile 在文件的基础内容上还增加了类型、文件系统等扩展信息:

class VFSAction : AnAction() {override fun actionPerformed(e: AnActionEvent) {val virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE)virtualFile?.let {it.refresh(true, true) {// 文件刷新完成Utils.info("""文件路径: ${it.path}文件类型: ${it.fileType}文件系统: ${it.fileSystem}文件后缀: ${it.extension}文件时间戳: ${it.timeStamp}""".trimIndent())}}}
}

监听文件变更事件

只需要在项目启动监听中注册我们的文件变更监听事件即可:

class ProjectStartListener: ProjectActivity {override suspend fun execute(project: Project) {project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {override fun after(events: MutableList<out VFileEvent>) {for (event in events) {Utils.info("变更的文件: ${event.file?.path}")}}})}}

VFS 的监听是应用级别,因此当打开多个项目的时候,每个项目中的文件变更事件都会被所有的项目共享,因此需要注意对文件进行过滤处理。

通过以上简单的代码我们就可以获取所有的文件变更事件,做我们想做的事了(例如统一在文件前增加版权信息)。

编辑器

在第四篇文章中为了实现对用户的编码活动的统计,我们简单使用了 Editor 对象,主要是对其文档和光标活动的监听。而本文文章则会详细介绍关于 Editor 中的一些基础概念,主要是几个模型对象和文档对象,在正式介绍前,先说一下获取 Editor 对象的两种方式:

// 通过 project 获取
var editor = FileEditorManager.getInstance(project).selectedEditor
// 通过 Action 的事件对象获取
val editor = e.getData(PlatformDataKeys.EDITOR)

下面正式开始介绍 Editor 中的一些对象

Document

document 对象主要提供了获取和替换文件文本的一些方法(还可以添加对文档内容的监听):

image-20240105154024382

下面会结合模型对象介绍其使用。

CaretModel(插入符模型)

CaretModel(插入符模型) 用于获取编辑器中光标所处的位置,如果细分还可以分为 Logical Position(逻辑位置)和 Visual Position(视觉位置)。下面以一个简单的代码样例展示区别:

val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
val caretModel = editor.caretModel
Utils.info("""逻辑位置:<br/>${"-".repeat(20)}<br/>行号: ${caretModel.logicalPosition.line + 1}<br/>列号: ${caretModel.logicalPosition.column + 1}<br/><br/>视觉位置:<br/>${"-".repeat(20)}<br/>行号: ${caretModel.visualPosition.line + 1}<br/>列号: ${caretModel.visualPosition.column + 1}<br/>
""".trimIndent())

image-20240122160210509

可以看出来逻辑位置对应光标在文件中所处的真实位置(5 行 1 列),而视觉位置则如表面意思,由于存在折叠代码块(中间3行),所以行号为 3。除此之外,可以看到上述代码中,不管是caretModel.logicalPosition还是caretModel.visualPosition在获取行号和列号的时候都在结果加 1,这是因为行号和列号在代码中是从 0 开始计算。这里要注意的是,上面的折叠代码只是影响视觉位置结果的一种情况,如果文件开启自动断行(Soft-Wrap line),也同样会影响视觉位置的结果:

image-20240122161633034

使用类似阿拉伯语这种右向文字也会影响逻辑位置和视觉位置的结果。

除了以上两种定位方式,我们还可以通过caretModel.offset直接获取光标所在位置的全局偏移量:

image-20240122162645983

这里可以看到第 3 行开始位置对应的偏移量是 22(20个显示的字符加上两个换行符,空白字符都会被统计)。

除了单光标,多光标的时候(例如按住 alt 结合鼠标左键进行多行选择),每一个光标的位置也是按照上述规则进行计算:

caretModel.allCarets.forEach { Utils.info("""偏移量:<br/>${"-".repeat(20)}<br/>偏移量: ${it.offset}<br/>""".trimIndent())
}

image-20240122163704843

最后以 Doucument 和 CaretModel 结合使用的例子结尾:

// 写操作需要放入 WriteCommandAction.runWriteCommandAction 中
WriteCommandAction.runWriteCommandAction(e.project) {// 插入字符串并移动光标位置到结尾val msg = "庄周de蝴蝶"document.insertString(caretModel.offset, msg)caretModel.moveToOffset(caretModel.offset + msg.length)
}

上述代码实现了在光标处插入指定文本信息并移动光标位置的效果。

InlayModel(嵌入模型)

InlayModel(嵌入模型)用于在代码行中嵌入各种信息,例如下图中msg:就属于嵌入信息,通过使用嵌入信息可以在不改变文本内容的情况下给用户一个直观的提示内容:

image-20240123143514386

除了上面这种使用方式,我们还可以使用 InlayModel 模拟各种 gpt 插件生成代码的效果,代码及效果如下(使用到了 caretModel 中的偏移量和视觉位置等内容):

// 清除所有嵌入信息
val inlayModel = editor.inlayModel
inlayModel.getInlineElementsInRange(0, editor.document.textLength).forEach { Disposer.dispose(it) }
inlayModel.getBlockElementsInRange(0, editor.document.textLength).forEach { Disposer.dispose(it) }
// 分别增加单行和多行嵌入信息
val offset = caretModel.offset
val column = caretModel.visualPosition.column
inlayModel.addInlineElement(offset, DemoRender(editor, "庄周de蝴蝶"))
inlayModel.addBlockElement(offset, InlayProperties(),DemoRender(editor, mutableListOf("first line", "second line", "third line"), column.toFloat()))
// 移动光标位置到初始位置
caretModel.moveToVisualPosition(VisualPosition(caretModel.visualPosition.line, column))class DemoRender<T>(private val editor: Editor,private val renderText: T,private var wordCount: Float = 0f
): EditorCustomElementRenderer {// 设置字体private val font = Font("Microsoft YaHei", Font.ITALIC, editor.colorsScheme.editorFontSize)override fun calcWidthInPixels(p0: Inlay<*>): Int {// 获取渲染内容的宽度, 如果是多行文本则取最长文本行的宽度return when (renderText) {is String -> calcTextWidth(renderText)is MutableList<*> -> renderText.maxOfOrNull { calcTextWidth(it.toString()) } ?: 0else -> 0}}override fun calcHeightInPixels(inlay: Inlay<*>): Int {// 获取渲染内容的高度, 如果是多行文本则需要将行高乘以行数return when (renderText) {is MutableList<*> -> super.calcHeightInPixels(inlay) * renderText.sizeelse -> super.calcHeightInPixels(inlay)}}override fun paint(inlay: Inlay<*>, g: Graphics, targetRegion: Rectangle, textAttributes: TextAttributes) {val g2 = g.create() as Graphics2DGraphicsUtil.setupAAPainting(g2)textAttributes.foregroundColor = JBColor.GRAYval lineHeight = editor.lineHeight.toDouble()val fontBaseline = ceil(font.createGlyphVector(getFontMetrics().fontRenderContext, "中文").visualBounds.height)val linePadding = (lineHeight - fontBaseline) / 2.0val offsetX = targetRegion.xval offsetY = targetRegion.y + fontBaseline + linePaddingval lineOffset = 0g2.font = fontg2.color = JBColor.GRAYwhen (renderText) {is String -> {g2.drawString(renderText, offsetX.toFloat(), (offsetY + lineOffset).toFloat())}is MutableList<*> -> {// 多行文本渲染的时候设置缩进val tabSize = editor.settings.getTabSize(editor.project)val startOffset = calcTextWidth("Z") * (wordCount + tabSize)renderText.forEach {g2.drawString(it.toString(), startOffset, (offsetY + lineOffset).toFloat())g2.translate(0.0, lineHeight)}}else -> return}g2.dispose()}private fun calcTextWidth(text: String): Int {return getFontMetrics().stringWidth(text) }private fun getFontMetrics(): FontMetrics {val editorContext = FontInfo.getFontRenderContext(editor.contentComponent)val context = FontRenderContext(editorContext.transform, AntialiasingType.getKeyForCurrentScope(false), editorContext.fractionalMetricsHint)return FontInfo.getFontMetrics(font, context)}
}

动画

这里需要知道 addInlineElement 用于添加单行的嵌入信息,addBlockElement 用于添加多行嵌入信息,渲染内容需要实现 EditorCustomElementRenderer 类中的 paint 方法即可。通过使用 Graphics2D 对象,我们可以添加文本,图形甚至是图片信息,这里就不再逐一介绍。

image-20240123145515809

SoftWrapModel(自动断行模型)

SoftWrapModel(自动断行模型)主要用于获取自动断行相关信息,其包含的方法较少,也很少会用到,简单了解即可,如下是一个简单的使用及效果:

val softWrapModel = editor.softWrapModel
Utils.info("""是否开启自动断行: ${softWrapModel.isSoftWrappingEnabled}<br/>当前行是否存在自动断行: ${softWrapModel.getSoftWrapsForLine(caretModel.offset).isNotEmpty()}<br/>
""".trimIndent())

image-20240123161016895

MarkupModel(标记模型)

MarkupModel(标记模型)主要用于用于将指定行(或者某个区间)设置为高亮,下面直接展示其使用方式和效果,很容易理解和使用:

val markupModel = editor.markupModel
markupModel.removeAllHighlighters()
// 设置当前行为红色高亮背景
val lineAttr = TextAttributes()
lineAttr.backgroundColor = JBColor.RED
val line = caretModel.logicalPosition.line
markupModel.addLineHighlighter(line, HighlighterLayer.ERROR, lineAttr)// 设置指定范围为绿色高亮背景
// 最后一个参数指定为 HighlighterTargetArea.EXACT_RANGE 则为精确范围
// 指定为 HighlighterTargetArea.LINES_IN_RANGE 则会将偏移量所在的整行都进行设置
val rangeAttr = TextAttributes()
rangeAttr.backgroundColor = JBColor.BLUE
markupModel.addRangeHighlighter(0, 6, HighlighterLayer.SELECTION, rangeAttr, HighlighterTargetArea.EXACT_RANGE)

动画

SelectionModel(选中模型)

SelectionModel(选中模型)用于处理编辑器中文本选中相关的操作,下面是一个简单的使用样例:

// 先移除所有文本选中, 然后将偏移量为 0 ~ 6 的内容进行选中
val selectionModel = editor.selectionModel
selectionModel.removeSelection(true)
selectionModel.setSelection(0, 6)
Utils.info("""选中的文本内容: ${selectionModel.selectedText}<br/>选中文本的开始和结束位置: (${selectionModel.selectionStart}, ${selectionModel.selectionEnd})<br/>
""".trimIndent())

动画

FoldingModel(折叠模型)

FoldingModel(折叠模型)用于对代码块进行折叠操作,通过结合 SelectionModel,可以很方便地对选中的文本进行折叠,下面是使用方式及效果:

val selectionModel = editor.selectionModel
val foldingModel = editor.foldingModel
// 批量折叠操作需要放在 runBatchFoldingOperation 中
foldingModel.runBatchFoldingOperation {// 移除所有的折叠foldingModel.allFoldRegions.forEach { foldingModel.removeFoldRegion(it) }// 对选中文本进行折叠并设置显示的提示文本foldingModel.addFoldRegion(selectionModel.selectionStart, selectionModel.selectionEnd, "庄周de蝴蝶")
}

动画

IndentsModel(缩进模型)

IndentsModel(缩进模型)用于获取文本中的缩进信息,使用方式也很简单,如下所示:

val indentsModel = editor.indentsModel
val guide = indentsModel.caretIndentGuide ?: return
Utils.info("""缩进级别: ${guide.indentLevel}<br/>开始行: ${guide.startLine + 1}<br/>结束行: ${guide.endLine + 1}<br/>
""".trimIndent())

动画

ScrollingModel(滚动模型)

ScrollingModel(滚动模型)可以用于获取编辑器的可视区域范围以及执行滚动操作,使用方法如下:

val scrollingModel = editor.scrollingModel
scrollingModel.scrollToCaret(ScrollType.CENTER)
scrollingModel.runActionOnScrollingFinished {scrollingModel.visibleArea.let {Utils.info("""可视区域左上角坐标: (${it.x}, ${it.y})<br/>可视区域宽度: ${it.width}<br/>可视区域高度: ${it.height}<br/>""".trimIndent())}
}

动画

编辑器事件

在第三篇文章中我们简单了解了 IntelliJ 中的事件监听,当时只介绍了项目的启用与关闭事件,这里再简单介绍一下关于编辑器的事件。

EditorActionHandler

IntelliJ 平台默认为我们提供了一些编辑器事件,例如复制、粘贴等,可以在IdeActions类中看到:

image-20240124141755359

相应地,使用方式也很简单,只需要实现 EditorActionHandler 类重写其中的 doExecute 方法即可,下面代码展示了对编辑器的复制事件进行监听并提示:

class EditorCopyListener: EditorActionHandler(), ProjectActivity {override suspend fun execute(project: Project) {// 注册复制监听EditorActionManager.getInstance().setActionHandler(IdeActions.ACTION_EDITOR_COPY, this)}override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {// 将选中的文本加入到粘贴板中, 这里使用前文中提到的 selectionModeleditor.selectionModel.copySelectionToClipboard()// 显示复制文本Messages.showInfoMessage("Copy text: ${CopyPasteManager.getInstance().getContents<String>(DataFlavor.stringFlavor)}", "Copy")}
}

动画

这里需要注意的是,在重写了编辑器的事件后(这里是复制事件),则该事件就不再生效了,因此这里通过editor.selectionModel.copySelectionToClipboard()这行代码手动了复制的操作,在重写其它编辑器事件的时候也需要注意这个问题。

TypedHandlerDelegate

除了使用 IntelliJ 中已经定义好的事件,还可以通过实现 TypedHandlerDelegate 来对键盘中输入的字符进行监听(无法监听空白字符,例如回车、TAB等,也无法在出现编码提示时进行监听):

class KeyboardListener: TypedHandlerDelegate() {override fun charTyped(c: Char, project: Project, editor: Editor, file: PsiFile): Result {Messages.showInfoMessage("Input character: $c", "Input")return Result.CONTINUE}}

动画

同时需要在plugin.xml中进行配置:

<extensions defaultExtensionNs="com.intellij"><typedHandler implementation="cn.butterfly.vfs.listener.KeyboardListener"/>
</extensions>

总结

本文主要介绍了关于 VFS 和编辑器相关的知识,其中编辑器相关的内容则需要重点关注,这是开发各类插件都离开的东西,毕竟我们开发的插件大多都要和编辑器里的代码打交道,同时如果有错误之处,也欢迎一起交流讨论。

题外话

本来准备下篇文章就开始介绍关于 PSI 的知识,不过想一下还是决定先写一篇在第三篇文章中提到的关于内部工具(能极大提升初学者开发插件的效率,我个人是这么认为的)使用的文章,然后再继续介绍关于 PSI 的内容。

相关文章:

IntelliJ IDE 插件开发 | (五)VFS 与编辑器

系列文章 IntelliJ IDE 插件开发 |&#xff08;一&#xff09;快速入门IntelliJ IDE 插件开发 |&#xff08;二&#xff09;UI 界面与数据持久化IntelliJ IDE 插件开发 |&#xff08;三&#xff09;消息通知与事件监听IntelliJ IDE 插件开发 |&#xff08;四&#xff09;来查收…...

金融OCR领域实习日志(一)

一、OCR基础 任务要求&#xff1a; 工作原理 OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;是指电子设备&#xff08;例如扫描仪或数码相&#xff09;检查纸上打印的字符&#xff0c;经过检测暗、亮的模式肯定其形状&#xff0c;而后用…...

CC++编译和链接介绍

介绍 C语言的编译和链接是将源代码转换为可执行文件的两个关键步骤。以下是详细的流程&#xff1a; 编译过程&#xff08;Compilation&#xff09; 预处理&#xff08;Preprocessing&#xff09;&#xff1a; 编译器首先对源代码进行预处理&#xff0c;这个阶段处理#include包…...

Element-UI中的el-upload插件上传文件action和headers参数

官网给的例子action都是绝对地址&#xff0c;我现在需要上传到自己后台的地址&#xff0c;只有一个路由地址/task/upload 根据 config/index.js配置&#xff0c;那么action要写成/api/task/upload&#xff0c;另外也可以传入函数来返回地址:action"uploadUrl()"。 …...

在IntelliJ IDEA中通过Spring Boot集成达梦数据库:从入门到精通

目录 博客前言 一.创建springboot项目 新建项目 选择创建类型​编辑 测试 二.集成达梦数据库 添加达梦数据库部分依赖 添加数据库驱动包 配置数据库连接信息 编写测试代码 验证连接是否成功 博客前言 随着数字化时代的到来&#xff0c;数据库在应用程序中的地位越来…...

docker相关

下载Ubuntu18.04文件64位&#xff08;32位安装不了MySQL&#xff09; https://old-releases.ubuntu.com/releases/18.04.4/?_ga2.44113060.1243545826.1617173008-2055924693.1608557140 Linux ubuntu16.04打开控制台&#xff1a;到桌面&#xff0c;可以按快捷键ctrlaltt 查…...

生产力工具|卸载并重装Anaconda3

一、Anaconda3卸载 &#xff08;一&#xff09;官方方案一&#xff08;Uninstall-Anaconda3-不能删除配置文件&#xff09; 官方推荐的方案是两种&#xff0c;一种是直接在Anaconda的安装路径下&#xff0c;双击&#xff1a; &#xff08;可以在搜索栏或者使用everything里面搜…...

大模型学习与实践笔记(十二)

使用RAG方式&#xff0c;构建opencv专业资料构建专业知识库&#xff0c;并搭建专业问答助手&#xff0c;并将模型部署到openxlab 平台 代码仓库&#xff1a;https://github.com/AllYoung/LLM4opencv 1&#xff1a;创建代码仓库 在 GitHub 中创建存放应用代码的仓库&#xff…...

Vulnhub靶机:FunBox 5

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;FunBox 5&#xff08;10.0.2.30&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/funb…...

性能优化(CPU优化技术)-NEON指令介绍

「发表于知乎专栏《移动端算法优化》」 本文主要介绍了 NEON 指令相关的知识&#xff0c;首先通过讲解 arm 指令集的分类&#xff0c;NEON寄存器的类型&#xff0c;树立基本概念。然后进一步梳理了 NEON 汇编以及 intrinsics 指令的格式。最后结合指令的分类&#xff0c;使用例…...

【极数系列】Flink环境搭建(02)

【极数系列】Flink环境搭建&#xff08;02&#xff09; 引言 1.linux 直接在linux上使用jdk11flink1.18.0版本部署 2.docker 使用容器部署比较方便&#xff0c;一键启动停止&#xff0c;方便参数调整 3.windows 搭建Flink 1.18.0版本需要使用Cygwin或wsl工具模拟unix环境…...

仓储管理系统——软件工程报告(需求分析)②

需求分析 一、系统概况 仓库管理系统是一种基于互联网对实际仓库的管理平台&#xff0c;旨在提供一个方便、快捷、安全的存取货物和查询商品信息平台。该系统通过在线用户登录查询&#xff0c;可以线上操作线下具体出/入库操作、查询仓库商品信息、提高仓库运作效率&#xff…...

立创EDA学习:PCB布局

参考内容 【PCB布线教程 | 嘉立创EDA专业版入门教程&#xff08;11&#xff09;】 https://www.bilibili.com/video/BV1mW4y1Z7kb/?share_sourcecopy_web&vd_sourcebe33b1553b08cc7b94afdd6c8a50dc5a 单路布线 遵循顺序 先近后远&#xff0c;先易后难 可以拖动让拐角缩小…...

tomcat与Apache---一起学习吧之服务器

Apache和Tomcat都是Web服务器&#xff0c;但它们有一些重要的区别。 Apache服务器是普通服务器&#xff0c;本身只支持HTML即普通网页。不过可以通过插件支持PHP&#xff0c;还可以与Tomcat连通&#xff08;单向Apache连接Tomcat&#xff0c;就是说通过Apache可以访问Tomcat资…...

Vue3的优势

Vue3和Vue2之间存在以下主要区别&#xff1a; 1. 性能优化&#xff1a;Vue3在内部进行了重写和优化&#xff0c;采用了新的响应式系统&#xff08;Proxy&#xff09;&#xff0c;相较于Vue2中的Object.defineProperty&#xff0c;更具性能优势。Vue3还对编译和渲染进行了优化&…...

鸿蒙开发案例002

1、目标需求 界面有增大字体按钮&#xff0c;每次点击增大字体按钮&#xff0c;“Hello ArkTS”都会变大 2、源代码 Entry Component struct Page {textValue: string Hello ArkTSState textSize: number 50myClick():void{this.textSize 4}build() {Row() {Column() {//…...

Git学习笔记(第9章):国内代码托管中心Gitee

目录 9.1 简介 9.1.1 Gitee概述 9.1.2 Gitee帐号注册和登录 9.2 VSCode登录Gitee账号 9.3 创建远程库 9.4 本地库推送到远程库(push) 9.5 导入GitHub项目 9.6 删除远程库 9.1 简介 9.1.1 Gitee概述 众所周知&#xff0c;GitHub服务器在国外&#xff0c;使用GitHub作为…...

使用k8s 配置 RollingUpdate 滚动更新实现应用的灰度发布

方案实现方式&#xff1a; RollingUpdate 滚动更新机制 当某个服务需要升级时&#xff0c;传统的做法是&#xff0c;先将要更新的服务下线&#xff0c;业务停止后再更新版本和配置&#xff0c;然后重新启动服务。 如果业务集群规模较大时&#xff0c;这个工作就变成了一个挑战…...

MATLAB知识点:mode :计算众数

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章 3.4.1节 mode &#xff1a;计算众数 众数是指一…...

【JavaWeb】MVC架构模式

文章目录 MVC是什么&#xff1f;一、M &#xff1a;Model 模型层二、V&#xff1a;View 视图层三、C&#xff1a;Controller 控制层四、非前后端分离MVC五、前后端分离MVC总结 MVC是什么&#xff1f; MVC&#xff08;Model View Controller&#xff09;是软件工程中的一种**软件…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

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

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)

错误一&#xff1a;yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因&#xff0c;后面把yaml.safe_dump直接替换成yaml.dump&#xff0c;确实能保存&#xff0c;但出现乱码&#xff1a; 放弃yaml.dump&#xff0c;又切…...

PH热榜 | 2025-06-08

1. Thiings 标语&#xff1a;一套超过1900个免费AI生成的3D图标集合 介绍&#xff1a;Thiings是一个不断扩展的免费AI生成3D图标库&#xff0c;目前已有超过1900个图标。你可以按照主题浏览&#xff0c;生成自己的图标&#xff0c;或者下载整个图标集。所有图标都可以在个人或…...