一文读懂微前端架构
nanshan 2024-12-02 23:31 8 浏览 0 评论
转载本文请注明出处:微信公众号EAWorld
前端开发在程序猿中无疑是一个比较苦逼的存在,作为一个前端开发,你必须要掌握Javascript,HTML,CSS这三大基础。Javascript作为网络时代最为重要的开发语言,由于其设计上的限制,一直在演进,经历了ES3,ES5,ES6(ECMAScript 2015)... ... 而简单的CSS也无法完成你复杂的需求,你需要Less/Sass/Sytlus来增强你的CSS的功能。这些还远远只是一小部分,你还需要了解:
- 你需要有网络的基本知识,和常见的API接口例如HTTP/REST/GraphQL
- 你需要知道浏览器的兼容性,什么IE,Chrome,Safari,Firefox,等等
- 你需要知道应用如何打包,了解 Webpack ,还有gulp, Babel, Parcel, Browserify, and Grunt等是怎么工作的
- 你需要熟悉HTML的DOM操作,和相关的工具库例如jquery和d3js
- 需要了解不同的框架和他们的优缺点 例如React,AngularJS和VueJS,这三大框架都不兼容,而且各自都有自己庞大的生态
- 虽然NodeJS主要用于后端开发,但是很多前端的工具链和这个相关,例如构建工具 npm,yarn ...
- 如果你的项目足够复杂,你需要引入Monorepo,使用例如lerna,nx等工具来管理多个项目的包和依赖
- 你需要掌握基本的前端测试工具和框架,例如:Jasmin,Jest,Selenium,Mocha等等
最可怕的是,这些东西都在飞快地发展和变化中,当你正忙于学习ES8,ES9,ES10的新特性的时候,今天我要和大家分享的希望不是压死前端开发小骆驼的最后一根稻草--微前端。
目录:
1.什么是微前端
2.为什么需要微前端,它有什么优势
3.如何实现微前端架构
4.运行时微前端的具体实现方式
5.微前端的问题和缺点
一、什么是微前端
而提到微前端就离不开微服务,大家对微服务都比较熟悉了,微服务允许后端体系结构通过松散耦合的代码库进行扩展,每个代码库负责自己的业务逻辑,并公开一个API,每个API均可独立部署,并且各自由不同的团队拥有和维护。
前端架构经历了从单体,到前后端分离,再到微服务,最终发展到现在的微前端的过程如下图所示:
微前端的思路是把微服务的架构引入到前端,其核心都是要能够以业务为单元构建端到端的垂直架构,使得单个的团队能够独立自主的进行相关的开发,同时又具备相当的灵活性,按需求来组成交付应用。
“微前端”一词最早于2016年底在ThoughtWorks 技术雷达中提出的。它将微服务的概念扩展到了前端世界。当前的趋势是构建一个功能强大且功能强大的浏览器应用程序(也称为单页应用程序),该应用程序位于微服务架构之上。随着时间的流逝,通常由一个单独的团队开发的前端层会不断增长,并且变得更加难以维护。
微前端背后的想法是将网站或Web应用程序视为由独立团队拥有的功能的组合。每个团队都有自己关心和专长的不同业务或任务领域。一个团队是跨职能的,并且从数据库到用户界面,端到端地开发其功能。
但是,这个想法并不新鲜。它与“单体系统”概念有很多共同点。在过去,类似的方法被称为“垂直系统的前端集成”。但是微前端显然是一个更友好,更轻巧的术语。
在微服务的架构中,后台的服务已经按照业务进行了分离,而前端仍然是一个单体构建,通过网关来调用不同的后台服务。这个微服务的思路是相违背的,这也就造成了你的后端团队是按照业务分割的,但是前端团队仍然是一个整体。微前端可以有效地改进这一点。
微前端的核心思路其实是远程应用程序,包含组件/模块/包的运行时加载。
如上图,对于用户而言,访问的是一个微前端的容器(container),容器加载运行在远程服务上的应用,把这些远程应用作为组件/模块/包在本地浏览器中加载。
- 组件是底层UI库的构建单元
- 模块是相应运行时的构建单元
- 包是依赖性解析器的构建单元
- 微前端是所提出的应用程序的构建块
二、为什么需要微前端?
它有什么优势?
在前面我们看到的微前端之前的架构,所有的前端还是一个单体,前端团队会依赖所有的服务或者后台的API,前端开发会成为整个系统的瓶颈。使用微前端,就是要让前端业务从水平分层变为垂直应用的一部分,进入业务团队,剥离耦合。
那么微前端有什么好处,为什么要采用微前端架构呢?
- 各个团队独立开发,相互不影响,独立开发、独立部署,微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
- 增量升级,在面对各种复杂场景时,通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略。因为是运行时加载,可以在没有重建的情况下添加,删除或替换前端的各个部分。
- 不受技术影响,每个团队都应该能够选择和升级其技术栈,而无需与其他团队进行协调。也就是说A应用可以用React,而B应用使用Vue,大家可以通过同一个微前端来加载
- 独立运行时,每个微应用之间状态隔离,运行时状态不共享。隔离团队代码,即使所有团队都使用相同的框架,也不要共享运行时。构建自包含的独立应用程序。不要依赖共享状态或全局变量。
- 建立团队命名空间,对于CSS,事件,本地存储和Cookies,可以避免冲突并阐明所有权。
因此,微前端和微服务的本质都是关于去耦合。而只有当应用程序达到一定规模时,这才开始变得更有意义。
三、如何实现微前端架构
微前端不是一个库,是一种前端架构的设计思路,要实现微前端,本质上就是在运行时远程加载应用。
实现微前端,有几个思路,从构建的角度来看有两种,编译时构建微前端和运行时构建微前端:
- 编译时微前端,通常将第三方库中的组件作为包,在构建时引入依赖。这种实现引入新的微前端需要重新编译,不够灵活。编译时的微前端可以通过Web Components,Monorepo等方式来实现。其中Monorepo非常流行,常见的工具有nx,rush,lerna等。
- 运行时微前端,是一次加载或通过延迟加载按需动态将微型前端注入到容器应用程序中时。当引入新的微前端的时候,不需要构建,可以动态在代码中定义加载。我眼中的微前端更多是指这种运行时加载的微前端,因为独立构建,部署和测试是我们对于“微”的定义。
从前后端责任分层来看,可以从前端或者后端来实现。
通过客户端框架来实现
微前端通常由客户端工具来支持实现(听上去好有道理),有许多支持客户端开发微前端的实现工具,包括:Piral,Open Components,qiankun,Luigi,Frint.js等。其中qiankun是蚂蚁金服开发的。
在客户端还可以通过辅助库的方式来实现,辅助库可以为共享依赖项,路由事件或不同的微前端及其生命周期来提供一些基础架构。
下面的一个示例是通过诸如导入映射或打包特定块等机制处理共享依赖关系。
相关的工具有Webpack5 Module Federation,Siteless,Single SPA,Postal.js等
通过服务器端实现
微前端并非只能在客户端来实现,类似于服务端渲染,同样可以通过服务端来实现。
服务端微前端的支持工具有:Mosaic,PuzzleJs,Podium,Micromono等。
好了,说了这么多我相信你是一脸懵逼的,到底怎么实现的?我们抛开架构不说,来看看到底如何实现吧。
四、运行时微前端的具体实现方式
Iframe
iframes是可以在html中嵌入另一个HTML。下面就是用iframe实现微前端的一个例子:
<!DOCTYPE html>
<html>
<body>
<iframe src="http://localhost:3006" width="600" height="900">
<p>Your browser does not support iframes.</p>
</iframe>
<iframe src="http://localhost:3007" width="600" height="900">
<p>Your browser does not support iframes.</p>
</iframe>
</body>
</html>
如果不考虑体验问题,iframe 几乎是最完美的微前端解决方案了。iframe 提供了浏览器原生的隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但它的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。这里的主要问题包括:
- url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- UI 不同步,DOM 结构不共享。
- 全局上下文完全隔离,内存变量不共享。
- 慢。每次子应用进入都需要次浏览器上下文的重建、资源重新加载。
所以虽然使用iframe可以实现远程加载的效果,但是因为这些限制,很少会有应用会使用。
Nginx路由
利用Ngix路由,我们可以把不同的请求路由到不同的微前端的应用。
例如Nginx的路由能力,在前端可以动态请求不同的后端应用,而每一个后端应用独立运行,前端可以把这些不同的后端应用加载,编排在一起。下面的代码是一个Nginx的配置,customers/users/admins分别表示了三个不同的应用,前端通过路由来加载位于不同服务的后端应用。
worker_processes 4;
events { worker_connections 1024; }
http {
server {
listen 80;
root /usr/share/nginx/html;
include /etc/nginx/mime.types;
location /app1 {
try_files $uri app1/index.html;
}
location /app2 {
try_files $uri app2/index.html;
}
location /app3 {
try_files $uri app3/index.html;
}
}
}
无论你采用哪一种的微前端架构,Nginx方向代理或者其它的API网关的解决方案都能够提供方便灵活的后端路由功能。但是通过这种方式,需要定义一个通用可扩展的路由规则,否则当引入新的应用的时候,还需要修改Nginx的路由配置,那就很不方便了。
Webpack 5 Module Federation
Webpack5 的Module Federation是一个令人兴奋的革新,它能够很方便的支持微前端的构建。模块联合允许JavaScript应用程序从另一个应用程序动态加载代码,并在此过程中能共享依赖关系。如果使用Module Federation的应用程序不具有联合代码所需的依赖关系,则Webpack将从该联合构建源中下载缺少的依赖关系。
在Module Federation的上下文中,启动代码是一种将运行时代码附加到远程容器启动序列的实施策略。这真的很有用,因为通过Hook无法访问ModuleFederation及其运行时,无法对其进行扩展或添加一行代码,这些代码可以像动态设置远程容器的公共路径那样进行操作。这在普通的webpack应用程序中是微不足道的,但是在一个无法访问的自定义运行时容器中却很难做到,该容器为模块联合远程编排提供了动力。简单来说,Module Federation注入一段运行时的代码来负责加载和编排远程的应用代码,并能够管理和加载远程应用的依赖。
下面是一个对应的例子:
module.exports = {
mode: 'development',
devServer: {
port: 8080,
},
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
microFrontEnd1: 'microFrontEnd1@http://localhost:8081/remoteEntry.js',
microFrontEnd2: 'microFrontEnd2@http://localhost:8082/remoteEntry.js', },
})
]
};
上面的代码是微前端的容器端的配置,容器负责加载其它远程应用的代码。这个例子里,它加载了两个远程应用。
module.exports = {
mode: 'development',
devServer: {
port: 8081,
},
plugins: [
new ModuleFederationPlugin({
name: 'microFrontEnd1',
filename: 'remoteEntry.js',
exposes: {
'./MicroFrontEnd1Index': './src/index',
},
}),
]
};
每一个微前端的Webpack配置如上。
利用ModuleFederationPlugin,remote可以用来加载远端的应用,而Expose可以把自己的组件暴露为远端组件。
在container中,只需要调用以下的代码来加载远端组件。
import 'microFrontEnd1/MicroFrontEnd1Index';
import 'microFrontEnd2/MicroFrontEnd2Index';
Module Federation的加载过程如上图所示:
- localhost 加载index.html
- main.js 是Module Federation的核心的编排代码,负责加载远程组件。
- remoteEntry.js 是Module Federation暴露的远程组件的代码
- src_ 是打包后的代码,其中 bootstrap_js是容器侧的代码,index_js是微前端侧的代码。
Module Federation实现了类似动态链接库的能力,可以在运行时加载远程代码,远程代码本质上是一个加载在window上的全局变量,Module Federation可以帮助解决依赖的问题。Javascrip作为上古语言,没有提供依赖管理,导致留给各路大神各种发挥的空间。
Module Federation的缺点就是依赖Webpack 5,包直接挂载为全局变量。
EMP微前端是基于Module Federation的微前端解决方案。
Single SPA
单页面应用是当今为Web应用的主流,区别于传统的多页面应用,整个SPA只有一个页面,其内容都是通过Javascript的功能来加载。
SPA是一个Web应用程序,仅包含一个HTML页面。提供动态更新,它允许在不刷新页面的情况下与页面进行交互。利用单页应用程序,可以显着降低服务器负载并提高加载速度,从而获得更好的用户体验,因为SPA仅在先前加载整个页面时才按需导入数据。
除了开发复杂,对于SEO不友好,但页面应用的最大技术缺陷是URL不适合共享,因为SPA只有一个地址。
single-spa是一个框架,用于将前端应用程序中的多个JavaScript微前端组合在一起。
使用single-spa构建前端可以带来很多好处,例如:
- 在同一页面上使用多个框架而无需刷新页面(React,AngularJS,Angular,Embe)
- 独立部署微前端
- 使用新框架编写代码,而无需重写现有应用程序
- 延迟加载代码可缩短初始加载时间
single-spa应用程序包含以下内容:
- single-spa根配置,用于呈现HTML页面和注册应用程序的JavaScript。每个应用程序都注册了以下三项内容:name,加载应用程序代码的函数,确定应用程序何时处于活动状态/非活动状态的函数,
- 打包成模块的单页应用程序的应用程序。每个应用程序必须知道如何从DOM引导,安装和卸载自身。传统SPA和Single-SPA应用程序之间的主要区别在于,它们必须能够与其他应用程序共存,因为它们各自没有自己的HTML页面。例如,React或Angular SPA应用程序。处于活动状态时,他们可以侦听url路由事件并将内容放在DOM上。处于不活动状态时,它们不侦听url路由事件,并且已从DOM中完全删除。
Single-SPA注册的应用程序拥有普通SPA所具有的所有功能,只是它没有HTML页面。SPA包含许多已注册的应用程序,每个应用程序都有其自己的框架。已注册的应用程序具有其自己的客户端路由和它们自己的框架/库。它们呈现自己的HTML,并且在安装时有完全的自由去做他们想做的任何事情。挂载的概念是指已注册的应用程序是否正在将内容放在DOM上。决定是否挂载已注册应用程序的是其活动功能。每当未挂载已注册的应用程序时,它都应保持完全休眠状态直到挂载。
Single SPA的样例代码如下:
1. 微前端代码:
import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: Root,
errorBoundary(err, info, props) {
// Customize the root error boundary for your microfrontend here.
return null;
},
});
export const { bootstrap, mount, unmount } = lifecycles;
Single SPA的微前端是纯的JS组件,不包含HTML,需要通过容器来加载。
2.容器的Root Config
在容器侧,需要通过Import Map或者Webpack来定义远程组件,并注册远程应用。
{
"imports": {
"@naughty/root-config": "//localhost:9000/naughty-root-config.js",
"@naughty/app": "//localhost:8500/naughty-app.js",
"react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js"
}
}
容器侧的HTML文件使用import map来定义远程依赖,其中root-config是编排代码,负责远程应用的注册和加载。同时需要定义所有共享的依赖,这里例子中是react和react-dom
import { registerApplication, start } from "single-spa";
registerApplication({
name: "@single-spa/welcome",
app: () =>
System.import(
"https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
),
activeWhen: ["/welcome"],
});
registerApplication(
'@naughty/app',
() => System.import('@naughty/app'),
location => location.pathname.startsWith('/app')
);
start({
urlRerouteOnly: true,
});
在root-config中,我们注册了两个远程应用,使用不同的url来加载。/welcome会加载welcome应用,而/app会加载我们的app应用。
Single SPA的核心是利用不同的URL路由来加载远程组件,它可以和Webpack(打包时构建依赖)或者Import Map(运行时使用浏览器导入依赖)一起工作。注意,不要在你的微前端中混用两种依赖机制。
Single SPA还提供一个layout 引擎,可以帮助你快速的构建微前端。
相比Module Federation,Single SPA的代码和生命周期的管理更清楚,提供清晰的接口,缺点是共享的依赖需要手工通过import map来管理。
要做一个好的微前端因为受限于浏览器和JS的一些特性,并不容易。除了我们今天分享的内容,还面临着诸多的挑战:如何解决css/js的冲突,使得组件和应用完全隔离;如何解决不同应用间的通信;如何处理路由;如何保证UI风格的统一等等。
五、微前端的问题和缺点
讲了这么多的优点和实现,那么微前端是不是解决前端开发问题的银弹呢?当然不是。所有的架构都是取舍和权衡,这个世界上并不存在银弹,微前端架构和微服务一样也存在他的弊端,单体架构未必就差。
1. 微前端的构建通常比较复杂,从工具,打包,到部署,微前端都是更为复杂的存在,天下没有免费的午餐,对于小型项目,它的成本太高。
2. 每个团队可以使用不同的框架,这个听上去很美,但是实际操作起来,除了要支持历史遗留的应用,它的意义不大。同时也为带来体验上的问题。可以远程加载不同的框架代码是一回事,把它们都用好是另一回事。
3. 性能上来看,如果优化得不好微前端的性能可能会存在问题,至少微前端框架是额外的一层加载。如果不同的微前端使用了不同的框架,那么每一个框架都需要额外的加载。
微前端架构还在发展之中,本文提到的iframe/nginx/module federation/single-spa只是诸多解决方案中的一小部分,前端的发展变化和生态系统实在是丰富,其他的方案诸如umd/乾坤,Piral,open comonent等等。当使用你也可以选择标准的Web Component或者ES Modules来构建微前端,但是这些标准的浏览器支持不是特别好,这个是前端开发永远的痛。(诅咒IE)
大家对于微前端有什么想法或者问题,欢迎一起讨论。
关于作者:陶刚,Splunk资深软件工程师,架构师,毕业于北京邮电大学,现在在温哥华负责Splunk机器学习云平台的开发,曾经就职于SAP,EMC,Lucent等企业,拥有丰富的企业应用软件开发经验,熟悉软件开发的各种技术,平台和开发过程,在商务智能,机器学习,数据可视化,数据采集,网络管理等领域都有涉及。
关于EAWorld:使能数字转型,共创数智未来!长按二维码关注!
相关推荐
- Let’s Encrypt免费搭建HTTPS网站
-
HTTPS(全称:HyperTextTransferProtocoloverSecureSocketLayer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入...
- 使用Nginx配置TCP负载均衡(nginx tcp负载)
-
假设Kubernetes集群已经配置好,我们将基于CentOS为Nginx创建一个虚拟机。以下是实验种设置的详细信息:Nginx(CenOS8Minimal)-192.168.1.50Kube...
- Nginx负载均衡及支持HTTPS与申请免费SSL证书
-
背景有两台minio文件服务器已做好集群配置,一台是192.168.56.41:9000;另一台是192.168.56.42:9000。应用程序通过Nginx负载均衡调用这两台minio服务,减轻单点...
- HTTPS配置实战(https配置文件)
-
原因现在网站使用HTTPS是规范操作之一,前些日子买了腾讯云服务,同时申请了域名http://www.asap2me.top/,目前该域名只支持HTTP,想升级为HTTPS。关于HTTPS的链接过程大...
- 只有IP地址没有域名实现HTTPS访问方法
-
一般来说,要实现HTTPS,得有个注册好的域名才行。但有时候呢,咱只有服务器的IP地址,没注册域名,这种特殊情况下,也能照样实现HTTPS安全访问,按下面这些步骤来就行:第一步,先确认公网...
- 超详解:HTTPS及配置Django+HTTPS开发环境
-
众所周知HTTP协议是以TCP协议为基石诞生的一个用于传输Web内容的一个网络协议,在“网络分层模型”中属于“应用层协议”的一种。在这里我们并不研究该协议标准本身,而是从安全角度去探究使用该协议传输数...
- Godaddy购买SSL之后Nginx配置流程以及各种错误的解决
-
完整流程:参考地址:https://sg.godaddy.com/zh/help/nginx-generate-csrs-certificate-signing-requests-3601生成NGI...
- Nginx从安装到高可用,一篇搞定(nginx安装与配置详解)
-
一、Nginx安装1、去官网http://nginx.org/下载对应的nginx包,推荐使用稳定版本2、上传nginx到linux系统3、安装依赖环境(1)安装gcc环境yuminstallgc...
- 阿里云免费证书申请,配置安装,使用tomcat,支持http/https访问
-
参数说明商品类型默认已选择云盾证书服务(无需修改)。云盾证书服务类型SSL证书服务的类型。默认已选择云盾SSL证书(无需修改),表示付费版SSL证书。如果您需要免费领取或付费扩容DV单域名证书【免费试...
- 你试过两步实现Nginx的规范配置吗?极速生成Nginx配置小工具
-
NGINX是一款轻量级的Web服务器,最强大的功能之一是能够有效地提供HTML和媒体文件等静态内容。NGINX使用异步事件驱动模型,在负载下提供可预测的性能。是当下最受欢迎的高性能的Web...
- 从零开始搭建HTTPS服务(搭建https网站)
-
搭建HTTPS服务的最初目的是为了开发微信小程序,因为wx.request只允许发起HTTPS请求,并且还必须和指定的域名进行网络通信。要从零开始搭建一个HTTPS的服务需要下面4...
- 群晖NAS使用官网域名和自己的域名配置SSL实现HTTPS访问
-
安全第一步,群晖NAS使用官网域名和自己的域名配置SSL实现HTTPS访问【新手导向】NAS本质还是一个可以随时随地访问的个人数据存储中心,我们在外网访问的时候,特别是在公网IP下,其实会面临着很多安...
- 让网站快速升级HTTPS协议提高安全性
-
为什么用HTTPS网络安全越来越受到重视,很多互联网服务网站,都已经升级改造为https协议。https协议下数据包是ssl/tcl加密的,而http包是明文传输。如果请求一旦被拦截,数据就会泄露产生...
- 用Https方式访问Harbor-1.9版本(https访问流程)
-
我上周在头条号写过一篇原创文章《Docker-Harbor&Docker-kitematic史上最详细双系统配置手册》,这篇算是它的姊妹篇吧。这篇文章也将用到我在头条写的另一篇原创文章的...
- 如何启用 HTTPS 并配置免费的 SSL 证书
-
在Linux服务器上启用HTTPS并配置免费的SSL证书(以Let'sEncrypt为例)可以通过以下步骤完成:---###**一、准备工作**1.**确保域名已解析**...
你 发表评论:
欢迎- 一周热门
-
-
极空间如何无损移机,新Z4 Pro又有哪些升级?极空间Z4 Pro深度体验
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
10个免费文件中转服务站,分享文件简单方便,你知道几个?
-
日本海上自卫队的军衔制度(日本海上自卫队的军衔制度是什么)
-
[常用工具] OpenCV_contrib库在windows下编译使用指南
-
UOS服务器操作系统防火墙设置(uos20关闭防火墙)
-
【系统配置】信创终端挂载NAS共享全攻略:一步到位!
-
Ubuntu系统Daphne + Nginx + supervisor部署Django项目
-
WindowsServer2022|配置NTP服务器的命令
-
- 最近发表
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)