基于drawio构建流程图编辑器
基于drawio构建流程图编辑器
drawio是一款非常强大的开源在线的流程图编辑器,支持绘制各种形式的图表,提供了Web端与客户端支持,同时也支持多种资源类型的导出。
描述
在我们平时写论文、文档时,为了更好地阐述具体的步骤和流程,我们经常会有绘制流程图的需求,这时我们可能会想到Visio,可能会想到ProcessOn,同时我们也许会因为Visio其庞大的体积望而却步,也会因为ProcessOn只有免费的几张图而处处掣肘,那么在此时我们就要请出我们的主角drawio了,对于单纯的使用人员,使用drawio可以获得一个简单免费无限空间的高级绘图工具,而对于进阶的开发人员,可以为自己和团队非常简单快速的搭建一个免费无限空间且功能强大的绘图工具,何乐而不为。
drawio项目的历史可以追溯至2005年,当时JGraph团队开始开发mxGraph,这是一个基于JavaScript与SVG的图表库,用于在Web应用程序中创建交互式图表,支持了Firefox 1.5和Internet Explorer 5.5。2012年,JGraph团队将已有的程序删除了Java applet相关的部分,并且从域名diagram.ly改为draw.io,这是因为创始人觉得io比ly更酷,而drawio则成为了一个基于mxGraph的图表编辑器,可以在浏览器中运行并创建图表,最初是一个内部工具,而后来mxGraph团队决定将其作为一个开源项目发布。在2020年JGraph团队处于安全和版权的考虑,将draw.io移至diagrams.net域,diagrams.net目前仍然是一个活跃的开源项目,拥有大量的用户和贡献者,支持多种图表类型,包括流程图、组织结构图、UML图等,同时还支持多种文件格式,包括XML、PNG、JPEG、PDF等。
集成drawio到我们自己的项目有很多优点,包括但不限于 开箱即用的能力、应用于生产环境的非常成熟的项目、开源项目、支持二次开发、强大的社区等等,但是同样的drawio也存在一些不足,从上边简单的概括实际上可以看出来这个项目的历史实际上是非常久远了,本身也没有支持ESM,有大量的原型链修改,如果看过相关源码可以发现实际上是非常复杂的,代码的可读性和可维护性都不是很好,同时也没有支持TypeScript,这些都是我们需要解决的问题。实际上,现代浏览器中更加流行的方案应该是完全基于Canvas绘制的画板,当然这种方式的成本会相当高,如果我们想以低成本的方式集成一个流程图编辑器到我们自己的项目,那么drawio是最好的选择之一。
那么问题来了,我们应该如何将drawio集成到自己的项目当中,我们在这里提供了两种方案,一种是独立编辑器,这种方式是将Npm包打包到自己的项目当中,另一种是嵌入drawio,这种方式是通过iframe与部署好的drawio项目进行通信,这两种方式都可以用来完成流程图的集成,文中描述的相关内容都在 Github | Editor DEMO 中。
独立编辑器
首先我们来研究下作为独立编辑器集成到我们自己项目当中的方式,我们先来看一下mxGraph项目,文档地址为https://jgraph.github.io/mxgraph/,可以看到mxGraph有.NET、Java、JavaScript三种语言的支持,在这里我们主要关注的是JavaScript的支持,在文档中实际上我们是可以找到相当多的Example,在这里我们需要关注的是Graph Editor这个示例。当我们打开这个示例https://jgraph.github.io/mxgraph/javascript/examples/grapheditor/www/index.html之后,可以发现这实际上是一个非常完整的编辑器项目,而且我们可以看到这个链接的地址是以.html结尾并且是部署在Github的Git Pages上的,这就意味着这个.html后缀不是由后端输出的而是一个完整的纯前端项目,那么在理论上我们就可以将其作为纯前端的包集成到我们自己的项目中。
当前我们开发前端都离不开Npm包,我们也更希望将这个包作为依赖直接集成到我们的项目当中,但是当我们查阅相关的代码之后,发现这并不是一个简单的工作,例如当我们打开Graph.js这个文件,可以惊奇地发现仅这一个文件的代码行数就高达11941行,更不用说实际上核心部分是包括如下10个核心类的。
Actions.js
Dialogs.js
Editor.js
EditorUi.js
Format.js
Graph.js
Menus.js
Shapes.js
Sidebar.js
Toolbar.js
而且如果我们仔细观察相关的变量命名可以发现,这十个核心类并不是打包或者混淆之后的代码,也就是说其本身就是以这种形式编写的,在我们进行二次开发的时候也会感觉到比较难以维护,至于TS的支持我们本身也不能奢求,毕竟这确实是个年代非常久远的项目,毕竟在最初开发的时候TypeScript可能都还没开始。另外可以说句题外话,如果目前有需要使用mxGraph作为基础从零开发新项目而不是想集成已有的项目,目前更推荐使用maxGraph来完成,mxGraph早已停止维护,而maxGraph尽可能提供与mxGraph相同的功能,是支持TypeScript、Tree Shaking、ES Module的现代化矢量图形库。
回到集成独立编辑器的问题上来,我们的目标是要Graph Editor,而这个编辑器又是以mxGraph为基础完成的,所以我们当前的第一步就是将mxGraph作为依赖安装,mxGraph是有npm包的,所以直接安装这个依赖就可以了,对于TS项目也是有@typed-mxgraph/typed-mxgraph包,再指定一下tsconfig.json的typeRoots配置项即可,实际上在这里我们并不是很关心TS定义,因为我们上边描述的主体模块都是JS定义的,当然在修一些BUG的时候还是很有用的。那么在安装好mxGraph主包以及TS定义之后,我们先定义好将要引用的模块,当然实际上在这里因为mxGraph并没有ESM所以没有Tree Shaking的支持,在这里主要的目的就是方便后续的模块引用以及初始化模块的配置。
import factory from "mxgraph";
declare global {
interface Window {
mxBasePath: string;
mxLoadResources: boolean;
mxForceIncludes: boolean;
mxLoadStylesheets: boolean;
mxResourceExtension: string;
}
}
window.mxBasePath = "static";
window.mxLoadResources = false;
window.mxForceIncludes = false;
window.mxLoadStylesheets = false;
window.mxResourceExtension = ".txt";
const mx = factory({
// https://github.com/jgraph/mxgraph/issues/479
mxBasePath: "static",
});
// 需要用到的模块再引用
// 实际上所有的模块依然都会被打包
export const {
mxGraph,
// ...
} = mx;
在编写这个引用模块时,由于mxGraph并没有ESM的支持,我考虑到使用maxGraph来作为平替,尝试一番最后还是失败了,应该是两个包之间依然存在一定的GAP,最终还是选择使用mxGraph,另外如果有必要的话可以配置externals来避免需要完整打包mxGraph,这方面配置在这里就不再赘述了。那么接下来的主要工作就是将Graph Editor部分引入进来,这一部分是最耗时也是最麻烦的一部分,在集成的过程中我们主要做了如下几件事:
- 将主模块拆离并集成到我们当前的项目中。这部分工作实际上比较简单,就是将需要用到的代码全部下载到我们自己的项目当中,当然一开始也是没什么头绪的,因为在不了解的情况下还是比较难以组织起来这部分代码的,另外项目用到了大量的
window对象上的值,如果不借助一些工具很难去查找到这么多未定义的变量,我们只是把代码拷贝过来也是无法直接运行起来的,需要解决所有这些诸如undef的问题,以及外部资源引用的问题才行。 - 处理所有资源文件,包括图片、样式模块,去除所有依赖路径的资源引用。这部分工作是要处理外部的资源引用,
Graph Editor实际上是有很多外部的资源引用的,包括多语言、图片等,而实际上我们在上边配置的诸如mxBasePath、mxResourceExtension等都是为了要处理外部资源,但是由于我们目前是更希望作为npm包来引用的,处理资源路径问题又相对比较麻烦,所以在这里我们采取的方案是将所有的图片资源都处理成了Base64直接集成进去,当然在这个过程中也修改了相关代码使其不会发起请求去加载外部资源,另外由于一些修改过程中的客观原因,在项目中图片资源分为了两种,一种是转换成了Base64的TS文件,一种是借助loader加载的资源,当然本质上是都是Base64的资源,在这里实现的目标就是不再发起外部资源的请求。 - 借助
ESLint精简部分代码,去除部分IE浏览器的支持,Prettier格式化各个模块的代码。这部分工作是个比较复杂的,首先是借助ESLint精简代码,在这里就是对核心模块逐步放开ESLint规则,依据这些规则修改相关代码,例如借助no-undef就可以找到所有未定义的模块,然后再处理这些模块的引用,通过no-unused-vars规则找到未使用的变量,由此来精简代码。我们现在都更加聚焦于现代浏览器,对于IE浏览器不希望再做额外的支持,于是在这里我们也去除了部分兼容IE的代码。借助于Prettier以及prettier/prettier规则我们可以将代码格式化,在格式化代码之后可以看到相关模块的实现会比较舒服,而且也解决了一些隐式的问题,并且以Graph.js核心类为例,代码量从11941行精简到了10637行。 - 处理多语言,目前支持
EN和ZH-CN两种语言的加载。这部分工作主要是多语言的支持,目前我们希望的是不再加载外部资源,那么多语言当然也不例外,在这里我们已经将相关的语言定义好,要加载哪种语言之需要在启动编辑器的时候,将语言模块的配置传入即可,此外由于所有的语言模块并不是都必须要加载的,在这里是通过按需加载的方式实现的,以减少包的体积,实际上我们的主包也更推荐以懒加载的方式载入到自己的项目当中。
在完成了上述的集成之后,我们就可以成功地将项目完整的启动了,但是在实际使用的过程中发现还是有一些BUG,比如我们打开Graph Editor最新的在线链接,可以发现Sketch样式是无效的,所以我们还需要对整个包做一些BUG的修复,在这里主要列举了三个BUG的修改,仅作参考。
外部加载模块问题,众所周知(或者没那么周知)mxGraph的很多模块都是挂载到window上的,这里的模块有多种类型,比如图形模块mxGraphModel、mxGeometry、mxCell等等,工具模块mxUtils、mxEvent、mxCodec等等,但是在这里我们是作为npm包引进的,我们是不希望污染全局变量的,而且我们通过xml来加载图形的时候是需要找到这些图形模块,否则是无法呈现出图形的,经过分析源码我们可以知道动态加载在mxCodec的decode方法上,于是我们需要在这里处理好模块这个加载函数,当然可能通过external的方式加载mxGraph模块包的方式直接挂在window上也是个可行的办法,但是在这里我们是重写了相关模块来实现的。
// https://github.com/maxGraph/maxGraph/issues/102
// https://github.com/jgraph/mxgraph/blob/master/javascript/src/js/io/mxCodec.js#L423
mxCodec.prototype.decode = function (node, into) {
this.updateElements();
let obj: unknown = null;
if (node && node.nodeType == mxConstants.NODETYPE_ELEMENT) {
let ctor: unknown = null;
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // 因为需要处理的`XML Node`可能不在`Window`上
ctor = mx[node.nodeName] || window[node.nodeName];
} catch (error) {
console.log(`NODE ${node.nodeName} IS NOT FOUND`, error);
}
const dec = mx.mxCodecRegistry.getCodec(ctor);
if (dec) {
obj = dec.decode(this, node, into);
} else {
obj = node.cloneNode(true);
obj && (obj as Element).removeAttribute("as");
}
}
return obj;
};
Sketch无效问题,如果我们打开Graph Editor最新的在线链接,可以发现Sketch样式是无效的,因为现在mxGraph是不再继续维护了,所以反馈BUG是无效的,实际上这个问题处理也比较简单,我们可以通过git回溯到功能正常的版本就可以了。
aa11697fbd5ba9f4bb
https://github.com/jgraph/mxgraph-js
Scroll与菜单的挂载子容器问题,这个问题比较尴尬,因为mxGraph一直是以一整个应用来设计的,但是当我们需要将其嵌入到其他应用中的时候,由于我们的滚动容器可能就是body,此时当我们已经将页面向下滚动了一部分,之后再打开流程图编辑器的话,就会发现我们没有办法正常拖拽画布或者选中图形了,并且菜单的位置计算也出现了错误,所以在这里需要保证相关的位置计算正确。
mxUtils.getScrollOrigin = function (node, includeAncestors, includeDocument) {
includeAncestors = includeAncestors != null ? includeAncestors : false;
includeDocument = includeDocument != null ? includeDocument : false;
const doc = node != null ? node.ownerDocument : document;
const b = doc.body;
const d = doc.documentElement;
const result = new mxPoint();
let fixed = false;
while (node != null && node != b && node != d) {
if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop)) {
result.x += node.scrollLeft;
result.y += node.scrollTop;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const style = mxUtils.getCurrentStyle(node);
if (style != null) {
fixed = fixed || style.position == "fixed";
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
node = includeAncestors ? node.parentNode : null;
}
if (!fixed && includeDocument) {
const origin = mxUtils.getDocumentScrollOrigin(doc);
result.x += origin.x;
result.y += origin.y;
}
return result;
};
// 处理菜单的挂载容器
mxPopupMenu.prototype.showMenu = function () {
container.appendChild(this.div);
mxUtils.fit(this.div);
};
// 处理菜单的挂载子容器
mxPopupMenu.prototype.showSubmenu = function (parent, row) {
if (row.div != null) {
row.div.style.left = parent.div.offsetLeft + row.offsetLeft + row.offsetWidth - 1 + "px";
row.div.style.top = parent.div.offsetTop + row.offsetTop + "px";
container.appendChild(row.div);
const left = parseInt(row.div.offsetLeft);
const width = parseInt(row.div.offsetWidth);
const offset = mxUtils.getDocumentScrollOrigin(document);
const b = document.body;
const d = document.documentElement;
const right = offset.x + (b.clientWidth || d.clientWidth);
if (left + width > right) {
row.div.style.left =
Math.max(0, parent.div.offsetLeft - width + (mxClient.IS_IE ? 6 : -6)) + "px";
}
mxUtils.fit(row.div);
}
};
最后,实际上由于没有TreeShaking,并且我们可能需要动态地加载图形,所以我们整个包体积还是比较大的,所以为了不影响应用的主体能力,我们还是建议使用懒加载的方式去加载编辑器,具体来说就是可以通过import type来引入类型,然后通过import()来加载模块。
import type * as DiagramEditor from "embed-drawio/dist/packages/core/diagram-editor";
import type * as DiagramViewer from "embed-drawio/dist/packages/core/diagram-viewer";
let editor: typeof DiagramEditor | null = null;
export const diagramEditorLoader = (): Promise<typeof DiagramEditor> => {
if (editor) return Promise.resolve(editor);
return Promise.all([
import(
/* webpackChunkName: "embed-drawio-editor" */ "embed-drawio/dist/packages/core/diagram-editor"
),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import(/* webpackChunkName: "embed-drawio-css" */ "embed-drawio/dist/index.css"),
]).then(res => (editor = res[0]));
};
let viewer: typeof DiagramViewer | null = null;
export const diagramViewerLoader = (): Promise<typeof DiagramViewer> => {
if (viewer) return Promise.resolve(viewer);
return Promise.all([
import(
/* webpackChunkName: "embed-drawio-viewer" */ "embed-drawio/dist/packages/core/diagram-viewer"
),
]).then(res => (viewer = res[0]));
};
嵌入drawio
在上边我们完成了基于mxGraph Example的流程图编辑器NPM包,但是毕竟mxGraph已经不再维护,而JGraph在mxGraph Example的基础上又扩展开发了drawio,这是个长期维护的项目,即使drawio不接受贡献,但是依旧不妨碍他的活跃,可以在这里体验drawio的部署版本https://app.diagrams.net/。
在这里我们更要关注的是如何将drawio嵌入到我们的应用当中,drawio提供了embed的方式来帮助我们集成到自己的应用中,通过iframe的方式利用postMessage进行通信,这样也不会受到跨域的限制,由此来实现编辑、导入导出的一系列功能。
https://www.drawio.com/blog/embedding-walkthrough
https://desk.draw.io/support/solutions/articles/16000042544
我们在这里通过简单封装通信的方式来实现drawio的嵌入,具体来说就是通过iframe的方式来加载drawio,当然因为网络问题,真正投入到生产环境的话还是需要私有化部署一套才可以,私有化部署了之后也可以进行二开,当然如果在网络可以支持的情况下直接使用drawio的部署版本也是有可行性的,最终的数据存储都会存储到我们自己的应用当中。
import { EditorEvents } from "./event";
import { Config, DEFAULT_URL, ExportMsg, MESSAGE_EVENT, SaveMsg } from "./interface";
export class EditorBus extends EditorEvents {
private lock: boolean;
protected url: string;
private config: Config;
protected iframe: HTMLIFrameElement | null;
constructor(config: Config = { format: "xml" }) {
super();
this.lock = false;
this.config = config;
this.url = config.url || DEFAULT_URL;
this.iframe = document.createElement("iframe");
}
public startEdit = () => {
if (this.lock || !this.iframe) return void 0;
this.lock = true;
const iframe = this.iframe;
const url =
`${this.url}?` +
[
"embed=1",
"spin=1",
"proto=json",
"configure=1",
"noSaveBtn=1",
"stealth=1",
"libraries=0",
].join("&");
iframe.setAttribute("src", url);
iframe.setAttribute("frameborder", "0");
iframe.setAttribute(
"style",
"position:fixed;top:0;left:0;width:100%;height:100%;background-color:#fff;z-index:999999;"
);
iframe.className = "drawio-iframe-container";
document.body.style.overflow = "hidden";
document.body.appendChild(iframe);
window.addEventListener(MESSAGE_EVENT, this.handleMessageEvent);
};
public exitEdit = () => {
this.lock = false;
this.iframe && document.body.removeChild(this.iframe);
this.iframe = null;
document.body.style.overflow = "";
window.removeEventListener(MESSAGE_EVENT, this.handleMessageEvent);
};
onConfig(): void {
this.config.onConfig
? this.config.onConfig()
: this.postMessage({
action: "configure",
config: {
compressXml: this.config.compress ?? false,
css: ".geTabContainer{display:none !important;}",
},
});
}
onInit(): void {
this.config.onInit
? this.config.onInit()
: this.postMessage({
action: "load",
autosave: 1,
saveAndExit: "1",
modified: "unsavedChanges",
xml: this.config.data,
title: this.config.title || "流程图",
});
}
onLoad(): void {
this.config.onLoad && this.config.onLoad();
}
onAutoSave(msg: SaveMsg): void {
this.config.onAutoSave && this.config.onAutoSave(msg.xml);
}
onSave(msg: SaveMsg): void {
this.config.onSave && this.config.onSave(msg.xml);
if (this.config.onExport) {
this.postMessage({
action: "export",
format: this.config.format,
xml: msg.xml,
});
} else {
if (msg.exit) this.exitEdit();
}
}
onExit(msg: SaveMsg): void {
this.config.onExit && this.config.onExit(msg.xml);
this.exitEdit();
}
onExport(msg: ExportMsg): void {
if (!this.config.onExport) return void 0;
this.config.onExport(msg.data, this.config.format);
this.exitEdit();
}
}
而在我们使用的时候,直接实例化对象并且进入编辑模式就可以了,另外drawio支持多种数据的导出,但是在这里还是推荐xmlsvg,简单来说就是这种数据结构是在svg标签的基础上携带了xml数据,这样的话作为部分冗余字段是可以直接展示为svg也可以直接将其导入到drawio再次编辑的,如果仅导出为svg则是不能再导入编辑的,如果只导出了xml虽然可以再次编辑,但是想作为svg展示的话就需要viewer.min.js来渲染,这部分还是看需求来决定导出类型比较合适。
const bus = new diagram.EditorBus({
data: svgExample,
format: "xmlsvg",
onExport: (svg: string) => {
const svgStr = base64ToSvgString(svg);
if (svgStr) {
setSVGExample(svgStr);
}
},
});
bus.startEdit();
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://github.com/jgraph/drawio
https://github.com/jgraph/mxgraph
https://github.com/maxGraph/maxGraph
https://github.com/jgraph/mxgraph-js
https://zh.wikipedia.org/wiki/Draw.io
https://juejin.cn/post/7017686432009420808
https://github.com/jgraph/drawio-integration
https://jgraph.github.io/mxgraph/javascript/index.html
基于drawio构建流程图编辑器的更多相关文章
- 基于NoCode构建简历编辑器
基于NoCode构建简历编辑器 基于NoCode构建简历编辑器,要参加秋招了,因为各种模版用起来细节上并不是很满意,所以尝试做个简单的拖拽简历编辑器. 描述 Github | Resume DEMO ...
- 基于slate构建文档编辑器
基于slate构建文档编辑器 slate.js是一个完全可定制的框架,用于构建富文本编辑器,在这里我们使用slate.js构建专注于文档编辑的富文本编辑器. 描述 Github | Editor DE ...
- Tridiv:基于 Web 的 CSS 编辑器,创建炫丽 3D 图形
Tridiv 是一个基于 Web 的编辑器,使用 CSS 创建 3D 形状.它提供了一个传统的四个面板的操作界面,给出了从每个平面的视图,以及一个预览窗格中示出的最终的效果.使用 Tridiv 可以创 ...
- Summernote – 基于 Bootstrap 的文本编辑器
Summernote 是一个简单,灵活,所见即所得(WYSIWYG)的编辑器,基于 jQuery 和 Bootstrap 构建.Summernote 所有主要的操作都支持快捷键,有一个功能强大的 AP ...
- 基于 WebSocket 构建跨浏览器的实时应用
Socket.IO – 基于 WebSocket 构建跨浏览器的实时应用 Socket.IO 是一个功能非常强大的框架,能够帮助你构建基于 WebSocket 的跨浏览器的实时应用.支持主流浏览器,多 ...
- 基于Kubernetes 构建.NET Core技术中台
今天下午在腾讯云+社区社区分享了<基于Kubernetes 构建.NET Core技术中台>,下面是演讲内容的文字实录. 我们为什么需要中台 我们现在处于企业信息化的新时代.为什么这样说呢 ...
- 一个基于mysql构建的队列表
通常大家都会使用redis作为应用的任务队列表,redis的List结构,在一段进行任务的插入,在另一端进行任务的提取. 任务的插入 $redis->lPush("key:task:l ...
- 基于Azure构建PredictionIO和Spark的推荐引擎服务
基于Azure构建PredictionIO和Spark的推荐引擎服务 1. 在Azure构建Ubuntu 16.04虚拟机 假设前提条件您已有 Azure 帐号,登陆 Azure https://po ...
- 基于LoadRunner构建接口测试框架
基于LoadRunner构建接口测试框架 http://www.docin.com/p-775544153.html
- 基于soapUI构建WebService测试框架
基于soapUI构建WebService测试框架 http://www.docin.com/p-775523285.html
随机推荐
- 二进制安装Kubernetes(k8s) v1.24.3 IPv4/IPv6双栈
二进制安装Kubernetes(k8s) v1.24.3 IPv4/IPv6双栈 Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes(k8s)二进制高可用安装部署,支 ...
- 用 Go 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分. 示例: 输入:nums = [1,2,3,4]输出:[1,3,2,4] 注:[3,1, ...
- Vue启用报错 RangeError: Invalid typed array length: -4095
近期开发的前端项目项目启用失败,记录下修复过程 RangeError: Invalid typed array length: -4095 错误原因:node版本问题,安装10.x.x 即可 重新安装 ...
- SQL concat_ws, collect_set, 和explode合并使用
1. 背景 有一个这样的数据集:字段和字段的值是两列 目的是将这个数据转换成规整的一个特征是一列的数据: 2. 做法 第一步:先造出列 select ucid ,CASE WHEN type ='性别 ...
- 可视化大屏:mapbox+vue全攻略
如题图,mapbox是一个支持真3D地形展示的webGIS框架,与常用的Leaflet.Cesium和Openlayers并称四大框架,本文将介绍mapbox-gl js 在 vue 中的用法. 为什 ...
- Netty介绍与认识
概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序. 2.体系结构图 Netty的核 ...
- Windows屏幕解锁服务原理及实现(1)
https://github.com/zk2013/windows_remote_lock_unlock_screen 将生成的DLL注册至注册表 HKEY_LOCAL_MACHINE\SOFTWAR ...
- 花式WA
如果WA了,请从下述所有方面全方面考察反思一下= =,(不断更新): 1.输出YES,NO不是N0,还有"."不要眼瞎= =,(只有空格回车不对才提示PE): 2.边界值,0,1, ...
- 前端Vue项目打包性能优化方案
一.前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 DOM 以及如何最高效地操作 DOM:但 Vue 项目中仍 ...
- 2021-01-03:java中,描述一下什么情况下,对象会从年轻代进入老年代?
福哥答案2021-01-03: 1.对象的年龄超过一定阀值,-XX:MaxTenuringThreshold 可以指定该阀值.2.动态对象年龄判定,有的垃圾回收算法,比如 G1,并不要求 age 必须 ...