一、罗里吧嗦

最近迁移了服务器,顺道完善下服役了一两年的Jenkins服务,主要是把Slave搭建起来,还有等等。本文只是我对Jenkins Pipeline的一些自己的理解与应用,欢迎指出错误,欢迎交流高级应用

二、运行环境

Jenkins:

  1. master:阿里云Windows_2016_x64
  2. Slave1:京东云Windows_2008_r2_x64
  3. Slave2:阿里云Windows_2008_r2_x86

版本管理器:自建的git服务器,使用gogs

.NET项目:使用VS2017新建的一个web mvc项目与一个windows service项目,项目上传至git服务器

一些辅助工具:

  1. 7-zip:作为压缩 解压
  2. ossutil:阿里云oss服务工具
  3. nuget:还原解决方案引用包
  4. MSBuild:编译项目

三、开始

首先新建.NET项目,新建一个web项目与windows service项目,步骤略

其次,在自行安装Jenkins,步骤略


新建Jenkins项目,类型选择Pipeline,命名为JenkinsPipelineProject

整体流程如下

start->检出代码->还原引用包->编译->打包->上传OSS->分发slave->发布web->发布Service->end

各步骤:

检出代码:使用内置的工具进行代码的检出,如我使用的是git

还原引用包:使用nuget.exe对解决方案进行引用包还原,包源可选国内节点,国内节点下载速度框

编译:此处进行了两次编译,一次编译web,一次编译Service

打包:并行进行,对编译步骤得到的文件进行打包(使用7zip),存放于本地路径上,打包时,会删除相关配置文件,配置文件为手动更新

上传OSS:对刚打包好的更新包进行上传,因两台服务器处于阿里云内网,所以采用阿里云的OSS,更新速度快

分发Slave:根据配置的节点,进行更新web和service操作

发布web:首先从OSS下载文件下来, 停止站点(非停止IIS),使用7zip进行解压文件,更新文件,更新完毕后启动站点,如有多台服务器需要更新,则并行执行,互不干扰

发布Service:首先从OSS下载文件下来,停止对应的windows服务,卸载对应的windows服务,如若失败,则进行强制删除windows服务,之后使用7zip进行文件的解压更新,更新完毕后安装服务,并启动服务

以下为具体的Pipeline代码

注:

  1. 代码中所需的配置为我自己本身项目需要,如若更改,可根据自己项目进行定制
  2. 代码中一些敏感的配置已用xxxx代替
  3. 仅用于参考
//编译服务器设置start
def buildNodeSettings = [:]
buildNodeSettings.node = '阿里云Windows_2008_r2_x86'//编译服务器节点设置
buildNodeSettings.gitUrl = 'https://xxx/JenkinsPipelineProject.git'//git地址
buildNodeSettings.gitBarnches = '*/master' //分支
buildNodeSettings.slnFile = 'JenkinsPipelineProject.sln' //Nuget还原解决方案名 buildNodeSettings.buildFileForWeb ='JenkinsPipelineProjectWeb\\JenkinsPipelineProjectWeb.csproj' //msbulid编译文件名 web
buildNodeSettings.msbuildArgForWeb = '/t:Rebuild /p:Configuration=Release;PublishProfile=FolderProfile;DeployOnBuild=true' //msbulid参数 web
buildNodeSettings.publishOutputForWeb = '\\JenkinsPipelineProjectWeb\\bin\\Release\\PublishOutput' //编译后发布的路径 web
buildNodeSettings.publishFileNameForWeb = env.JOB_NAME + '/Build-Web-' +env.BUILD_NUMBER + '.7z' //文件名
buildNodeSettings.delFilesForWeb = ["Web.config","Web.Debug.config","Web.Release.config"] as String[] //需要删除的文件 buildNodeSettings.buildFileForService ='JenkinsPipelineProject.sln' //msbulid编译文件名 Service
buildNodeSettings.msbuildArgForService = '/t:Rebuild /p:Configuration=Release' //msbulid参数 Service
buildNodeSettings.publishOutputForService = '\\JenkinsPipelineProjectWindowsService\\bin\\Release' //编译后发布的路径 Service
buildNodeSettings.publishFileNameForService = env.JOB_NAME + '/Build-Service-' +env.BUILD_NUMBER + '.7z' //文件名
buildNodeSettings.delFilesForService = ["*.config"] as String[] //需要删除的文件 buildNodeSettings.updateServerPath = 'D:\\WebRoot\\update\\public_html\\'//更新服务器存放包地址
//编译服务器设置end def webNodeSetting = [:]
webNodeSetting.node = '阿里云Windows_2008_r2_x86' //Web服务器节点
webNodeSetting.downloadPath = 'C:\\Jenkins\\download\\'//更新包下载地址
webNodeSetting.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Web' //web服务器网站根目录
webNodeSetting.webApplicationName = 'JenkinsPipelinePorject'//web站点名称 def webNodeSetting2 = [:]
webNodeSetting2.node = 'master' //Web服务器节点
webNodeSetting2.downloadPath = 'C:\\JenkinsDownload\\'//更新包下载地址
webNodeSetting2.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Web' //web服务器网站根目录
webNodeSetting2.webApplicationName = 'JenkinsPipelinePorject'//web站点名称 def webNodeSetting3 = [:]
webNodeSetting3.node = '京东云Windows_2008_r2_x64' //Web服务器节点
webNodeSetting3.downloadPath = 'C:\\Jenkins\\download\\'//更新包下载地址
webNodeSetting3.publishPath = 'C:\\WebRoot\\JenkinsPipelinePorject\\Web' //web服务器网站根目录
webNodeSetting3.webApplicationName = 'JenkinsPipelinePorject'//web站点名称 def serviceNodeSetting = [:]
serviceNodeSetting.node = '阿里云Windows_2008_r2_x86'
serviceNodeSetting.downloadPath = 'C:\\Jenkins\\download\\'//更新包下载地址
serviceNodeSetting.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Service' //Service Windows Service存放路径
serviceNodeSetting.serviceName = 'JenkinsPipelineProject'//服务名称
serviceNodeSetting.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服务的文件名,相对publishPath的路径 def serviceNodeSetting2 = [:]
serviceNodeSetting2.node = 'master'
serviceNodeSetting2.downloadPath = 'C:\\Jenkins\\download\\'//更新包下载地址
serviceNodeSetting2.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Service' //Service Windows Service存放路径
serviceNodeSetting2.serviceName = 'JenkinsPipelineProject'//服务名称
serviceNodeSetting2.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服务的文件名,相对publishPath的路径 def serviceNodeSetting3 = [:]
serviceNodeSetting3.node = '京东云Windows_2008_r2_x64'
serviceNodeSetting3.downloadPath = 'C:\\Jenkins\\download\\'//更新包下载地址
serviceNodeSetting3.publishPath = 'C:\\WebRoot\\JenkinsPipelinePorject\\Service' //Service Windows Service存放路径
serviceNodeSetting3.serviceName = 'JenkinsPipelineProject'//服务名称
serviceNodeSetting3.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服务的文件名,相对publishPath的路径 node(buildNodeSettings.node) { def msbuild=tool name: 'MSBuildTool V14.0', type: 'msbuild' //编译工具名称与地址
buildNodeSettings.publishOutputForWeb = env.WORKSPACE + buildNodeSettings.publishOutputForWeb
buildNodeSettings.publishOutputForService = env.WORKSPACE + buildNodeSettings.publishOutputForService stage('Check Out')
{
echo '检出项目'
checkout([$class: 'GitSCM', branches: [[name: buildNodeSettings.gitBarnches]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'xxxxxx', url: buildNodeSettings.gitUrl]]])
} stage('Nuget Restore')
{
echo ' 还原nuget '
echo '${env.nuget} restore "' + env.WORKSPACE + '/' + buildNodeSettings.slnFile + '" -ConfigFile "' + env.config + '" -NoCache'
bat env.nuget + ' restore "' + env.WORKSPACE + '/' + buildNodeSettings.slnFile + '" -ConfigFile "' + env.config + '" -NoCache'
} stage('Bulid')
{
echo ' 编译项目'
echo 'Bulid Web'
bat '"' + msbuild + '" ' + buildNodeSettings.msbuildArgForWeb + ' "' + env.WORKSPACE + '/' + buildNodeSettings.buildFileForWeb + '"'
echo 'Bulid Service'
bat '"' + msbuild + '" ' + buildNodeSettings.msbuildArgForService + ' "' + env.WORKSPACE + '/' + buildNodeSettings.buildFileForService + '"'
} stage('Pack') {
parallel PackWeb:{
echo '删除相关配置文件'
buildNodeSettings.delFilesForWeb.each{
echo '删除文件:' + it
def filepath ='"' + buildNodeSettings.publishOutputForWeb.replace("/","\\") + '\\' + it + '"'
bat 'if exist '+ filepath +' del ' + filepath
}
echo ' 发布到更新系统'
bat 'if not exist "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '" md "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '"'
bat '"' + env.zip + '"'+ ' a -r "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForWeb + '" "' + buildNodeSettings.publishOutputForWeb + '\\*"'
echo '压缩完成'
echo '上传oss'
bat env.oss + ' -c ' + env.ossconfig + ' cp "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForWeb + '" "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb +'"'
},
PackService:{
echo '删除相关配置文件'
buildNodeSettings.delFilesForService.each{
echo '删除文件:' + it
def filepath ='"' + buildNodeSettings.publishOutputForService.replace("/","\\") + '\\' + it + '"'
bat 'if exist '+ filepath +' del ' + filepath
}
echo ' 发布到更新系统'
bat 'if not exist "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '" md "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '"'
bat '"' + env.zip + '"'+ ' a -r "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForService + '" "' + buildNodeSettings.publishOutputForService + '\\*"'
echo '压缩完成'
echo '上传oss'
bat env.oss + ' -c ' + env.ossconfig + ' cp "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForService + '" "oss://xxxx/' + buildNodeSettings.publishFileNameForService +'"'
}
} stage('Clear')
{
echo '清理工作目录'
deleteDir()
}
} stage('Publish Web')
{
parallel publishWeb1:{
node(webNodeSetting.node)
{
echo '发布web'
echo '更新文件'
echo '更新文件下载地址为:http://xxxx/' + buildNodeSettings.publishFileNameForWeb
echo '下载文件'
bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting.downloadPath
echo '文件下载完成'
echo '停止站点'
bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe stop site "' + webNodeSetting.webApplicationName + '"'
bat '"' + env.zip + '" x "'+ webNodeSetting.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting.publishPath + '"'
echo '启动站点'
bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe start site "' + webNodeSetting.webApplicationName+ '"'
}
},
publishWeb2:{
node(webNodeSetting2.node)
{
echo '发布web'
echo '更新文件'
echo '更新文件下载地址为:http://xxxx/' + buildNodeSettings.publishFileNameForWeb
echo '下载文件'
bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting2.downloadPath
echo '文件下载完成'
echo '停止站点'
bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe stop site "' + webNodeSetting2.webApplicationName + '"'
bat '"' + env.zip + '" x "'+ webNodeSetting2.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting2.publishPath + '"'
echo '启动站点'
bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe start site "' + webNodeSetting2.webApplicationName+ '"'
}
},
publishWeb3:{
node(webNodeSetting3.node)
{
withEnv(['oss=C:\\Tools\\oss\\ossutil.exe', 'ossconfig=C:\\Tools\\oss\\config']) {//需要手动设置变量
echo '发布web'
echo '更新文件'
echo '更新文件下载地址为:http://xxxx/' + buildNodeSettings.publishFileNameForWeb
echo '下载文件'
bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting3.downloadPath
echo '文件下载完成'
echo '停止站点'
bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe stop site "' + webNodeSetting3.webApplicationName + '"'
bat '"' + env.zip + '" x "'+ webNodeSetting3.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting3.publishPath + '"'
echo '启动站点'
bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe start site "' + webNodeSetting3.webApplicationName+ '"'
}
}
}
} stage('Publish Service')
{
parallel publishService1:
{
node(serviceNodeSetting.node){ //发布windows service
echo '发布Service'
echo '下载文件'
bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting.downloadPath
echo '卸载Windows Services'
try{
bat 'net stop ' + serviceNodeSetting.serviceName
bat env.InstallUtil + ' -u ' + serviceNodeSetting.serviceName
}catch(ex)
{
echo '卸载失败:' + ex
try{
bat 'sc delete ' + serviceNodeSetting.serviceName
}catch(ex2)
{
echo '强制删除失败:' +ex2
}
}
echo '解压文件'
bat '"' + env.zip + '" x "'+ serviceNodeSetting.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting.publishPath + '"'
echo '服务安装'
bat env.InstallUtil + ' ' + serviceNodeSetting.publishPath + '\\' + serviceNodeSetting.serviceFileName + ' /name='+ serviceNodeSetting.serviceName + ' /display=' + serviceNodeSetting.serviceName + ' /desc=' + serviceNodeSetting.serviceName
echo '启动服务'
bat 'net start ' + serviceNodeSetting.serviceName }
},
publishService2:
{
node(serviceNodeSetting2.node){ //发布windows service
echo '发布Service'
echo '下载文件'
bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting2.downloadPath
echo '卸载Windows Services'
try{
bat 'net stop ' + serviceNodeSetting2.serviceName
bat env.InstallUtil + ' -u ' + serviceNodeSetting2.serviceName
}catch(ex)
{
echo '卸载失败:' + ex
try{
bat 'sc delete ' + serviceNodeSetting2.serviceName
}catch(ex2)
{
echo '强制删除失败:' +ex2
}
}
echo '解压文件'
bat '"' + env.zip + '" x "'+ serviceNodeSetting2.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting2.publishPath + '"'
echo '服务安装'
bat env.InstallUtil + ' ' + serviceNodeSetting2.publishPath + '\\' + serviceNodeSetting2.serviceFileName + ' /name='+ serviceNodeSetting2.serviceName + ' /display=' + serviceNodeSetting2.serviceName + ' /desc=' + serviceNodeSetting2.serviceName
echo '启动服务'
bat 'net start ' + serviceNodeSetting2.serviceName }
},
publishService3:
{
node(serviceNodeSetting3.node){
withEnv(['oss=C:\\Tools\\oss\\ossutil.exe', 'ossconfig=C:\\Tools\\oss\\config']) {//需要手动设置变量
//发布windows service
echo '发布Service'
echo '下载文件'
bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting3.downloadPath
echo '卸载Windows Services'
try{
bat 'net stop ' + serviceNodeSetting3.serviceName
bat env.InstallUtil + ' -u ' + serviceNodeSetting3.serviceName
}catch(ex)
{
echo '卸载失败:' + ex
try{
bat 'sc delete ' + serviceNodeSetting3.serviceName
}catch(ex2)
{
echo '强制删除失败:' +ex2
}
}
echo '解压文件'
bat '"' + env.zip + '" x "'+ serviceNodeSetting3.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting3.publishPath + '"'
echo '服务安装'
bat env.InstallUtil + ' ' + serviceNodeSetting3.publishPath + '\\' + serviceNodeSetting3.serviceFileName + ' /name='+ serviceNodeSetting3.serviceName + ' /display=' + serviceNodeSetting3.serviceName + ' /desc=' + serviceNodeSetting3.serviceName
echo '启动服务'
bat 'net start ' + serviceNodeSetting3.serviceName
}
}
}
}

以上代码对三台服务器上的web和service进行了更新操作,两台阿里云内网,一台京东云

代码说明:

node:节点,Slave,表示在哪个节点中运行
stage:阶段,表示当前阶段,可定义阶段名称
checkout:代码检出
echo:输出信息
bat:执行cmd命令,linux下命令为sh
env:环境变量,有系统定义变量和自定义变量两部分
parallel:表示并行执行步骤
更多详细解释请查看官方文档https://jenkins.io/doc/book/pipeline/

四、看看效果

我们开始构建刚才新建的项目

从gif可以看出,整个流程只耗费了一分钟不到,我们去看看这三台服务器

三台服务器的文件都已更新,并且服务已经启动,证明我们的pipeline代码是可行的

五、补充和改进

  • 这篇文章的代码量过大,语言组织能力有待改进
  • Slave节点的环境变量不能正确读取到,目前只能使用withEnv进行更改环境变量,具体情况publishService3
  • 项目的耦合性太高了,目前编译、打包、发布都在同一个项目中,需要进行项目的拆分
  • 没有加重试机制,一旦某一阶段失败,只能重新运行,有待改进
  • 失败邮件通知,这个目前没有加入

如若有写的不好的地方,请指出

如若有更好的方案,欢迎一起交流

如若有不懂,欢迎咨询,我会告诉你我知道的

本文已同步个人博客,欢迎转载

.NET项目从CI到CD-Jenkins_Pipeline的应用的更多相关文章

  1. Docker最全教程之使用TeamCity来完成内部CI、CD流程(十六)

    本篇教程主要讲解基于容器服务搭建TeamCity服务,并且完成内部项目的CI流程配置.教程中也分享了一个简单的CI.CD流程,仅作探讨.不过由于篇幅有限,完整的DevOps,我们后续独立探讨. 为了降 ...

  2. 理解 CI 和 CD 之间的区别(翻译)

    博客搬迁至https://blog.wangjiegulu.com RSS订阅:https://blog.wangjiegulu.com/feed.xml 原文链接:https://blog.wang ...

  3. 阿里巴巴CI:CD之分层自动化实践之路

    阿里巴巴CI:CD之分层自动化实践之路 2018-05-30 摘自:阿里巴巴CI:CD之分层自动化实践之路 目录 1 自动化  1.1 为什么要做自动化?  1.2 自动化的烦恼  1.3 自动化的追 ...

  4. CI、CD和dev-ops概念

    传统的开发方式是:需求方提供文档,实现方按照文档一步步开发,中间很少变动和修改. 但是随着市场的变化,产品更新迭代的加快,也要求开放方更快的响应变化,用最短的时间开发,部署上线. 这样,持续集成(CI ...

  5. CI与CD之Docker上安装Jenkins

    一.CI,CD,Jenkins的介绍 CI:持续集成(Continuous integration,简称 CI),在传统的软件开发环境中,有集成,但是没有持续集成这种说法,长时间的分支与主干脱离,导致 ...

  6. 在Jenkins的帮助下让我们的应用CI与CD

    上图三位大家应该很熟悉吧,借助这三者可以让我们的服务在Linux环境下持续集成.容器中持续部署. 本篇博客的项目是core webapi, .NET 5.0 在11号已经正式发布了,你们的项目都升级了 ...

  7. CI和CD的意思

    openstack中CI和CD的意思: 持续集成(CI)和持续交付(CD)

  8. Jenkins使用总结,2.0 新时代:从 CI 到 CD

    Jenkins近阶段使用的总结篇,只写了个引子,却一直未动手写完,今天补上. 前几篇文章提到在内网jenkins直接构建部署升级线上环境,job都是暴露在外面,很容易被误操作,需要做简单的权限控制,以 ...

  9. 【转载】详解CI、CD相关概念

    在软件的编译发布的过程中,经常能够看到CI.CD这样的词语.其实他们是专业的缩写短语,这里介绍下他们的概念和区别. 敏捷软件开发 敏捷软件开发,英文全称:Agile software developm ...

  10. Kubernetes CI/CD(2)

    本章节通过在Jenkins创建一个kubernetes云环境,动态的在kubernetes集群中创建pod完成pipeline的构建流程,关于直接在宿主机上搭建Jenkins集群的可参照Kuberne ...

随机推荐

  1. JS组件系列——基于Bootstrap Ace模板的菜单Tab页效果优化

    前言:之前发表过一篇  JS组件系列——基于Bootstrap Ace模板的菜单和Tab页效果分享(你值得拥有) ,收到很多园友的反馈,当然也包括很多诟病,因为上篇只是将功能实现了,很多细节都没有处理 ...

  2. const的用法,特别是用在函数前面与后面的区别!

    const的用法,特别是用在函数后面 在普通的非 const成员函数中,this的类型是一个指向类类型的 const指针.可以改变this所指向的值,但不能改变 this所保存的地址. 在 const ...

  3. hdu2157矩阵快速幂

    How many ways?? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  4. NOIP2017SummerTraining0714

    个人感受:第一题做了字典树,还运行错误,然后就弃疗了,然后水了二三两题,总共拿了85分,倒数. 正确答案 时间限制: 2 Sec  内存限制: 256 MB提交: 702  解决: 82[提交][状态 ...

  5. 【机器学习实战】第6章 支持向量机(Support Vector Machine / SVM)

    第6章 支持向量机 <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/lates ...

  6. UIAlertController基本使用与内存泄露分析!!!

    最近开发过程中,发现内存会无故增加,在做内存优化的过程中,无意间发现了内存泄露的情况,那就是从iOS8.0 苹果开始推荐我们使用的UIAlertController!!! 看到这你是不是会嘲笑我第一次 ...

  7. 57、Bootstrap中文文档

    给大家介绍一个前端框架让你从此写起前端代码与之先前相比如有神助般的效果拉就是Bootstrap. 一.Bootstrap的下载 Bootstrap,由Twitter的设计师Mark Otto和Jaco ...

  8. linux下c语言的多线程编程

    我们在写linux的服务的时候,经常会用到linux的多线程技术以提高程序性能 多线程的一些小知识: 一个应用程序可以启动若干个线程. 线程(Lightweight Process,LWP),是程序执 ...

  9. ZOJ2110 HDU1010 搜索 Tempter of the Bone

    传送门:Tempter of the Bone 大意是给一个矩阵,叫你是否可以在给定的可走路径上不重复地走,在最后一秒走到终点. 我用了两个剪枝,且称其为简直001和剪枝002,事实证明001不要都可 ...

  10. Echarts数据可视化dataZoom,开发全解+完美注释

    全栈工程师开发手册 (作者:栾鹏) Echarts数据可视化开发代码注释全解 Echarts数据可视化开发参数配置全解 6大公共组件详解(点击进入): title详解. tooltip详解.toolb ...