今天给大家带来的分享是基于支付场景的一个微服务实战,会更偏向于应用层的内容。 主要围绕以下四点进行分享: SOA 与微服务 老支付架构遇到的挑战 基于微服务怎么做的改造 未来计划要做的事 SOA 与微服务 在我看来,微服务虽是国外传进来的技术,却和咱们中国的一些理论是挂钩的。所以在正式进入主题之前,先给大家简单介绍一下麦田理论。 关于麦田理论 古代周朝时期,老百姓种地实际是没有任何规划的,也没有任何区域的限制。 一般来说在地里一会种水稻,一会种小麦,一会种蔬菜地交叉来种,可收成之后发现庄稼受阳光程度非常低,营养非常不均衡,后期维护成本非常高。 直到战国时期,有一位农业专家把地划分为多个区域,每一个区域种一种庄稼,地跟地隔开,形成最初的微服务理念。 过去我们看到的很多文章都只是讲到 SOA 和微服务之间的比较,我今天在这个基础上加了一个 DDD。下面就 DDD、SOA 以及微服务的演进过程先做个引子。 DDD、SOA 与微服务 SOA 架构 SOA 是上一个时代的产物,大概是在 2010 年之前出现的,最早提出时是提供给传统行业计算领域的解决方案,当时 Oracle、IBM 也提了很多方案,包括出现的很多流程引擎。 它的思想是将紧耦合的系统,划分为面向业务的粗粒度、松耦合、无状态的服务。 在这之后,微服务的提出者基于 SOA 做了一个改进,就把它变成单一职责、独立部署、细小的微服务,是一个相反的概念。 微服务与 DDD 今天我们一说到微服务就会想到 DDD,有不少朋友认为 DDD 就是为微服务而生的。其实不是这样的,我在接触 DDD 时,它最早是用来做 UML 设计、领域建模的。 DDD 讲究充血模型,而 J2EE 模型以传统的分层架构和 Spring 架构捆绑在一起形成了以贫血模型为主的架构模式。 贫血模型的优点是容易入门、分层清晰,而充血模型要求设计者前期对业务理解较深,不然后期项目会产生混乱。 另外就是 DDD 思想比较宽泛,导致形成百家争鸣的姿态,没有形成一套固定的方法论。 开发者不容易理解,所以后面关注 DDD 的人变少了,而微服务的提出巧妙地借鉴了 DDD 里面的限界上下文、子域、领域事件等关键词,在微服务得到越来越多业界认可的情况下,也给 DDD 带来了重新的焕发机会。 老支付架构遇到的挑战 判断项目好坏的两个角度 我们判断一个优秀项目的好坏,可以从优秀的代码和高可用架构两个方向来讲。 我们在设计高可用架构的同时,也不能忽视代码的重要性,优秀的代码指的是冗错能力、冥等操作、并发情况、死锁情况等,并不一定是指代码写得多漂亮。 这就好比盖楼一样,楼房的基础架子搭得很好,但盖房的工人不够专业,有很多需要注意的地方忽略了,那么在往里面填砖加瓦的时候出了问题。 后果就是房子经常漏雨,墙上有裂缝等各种问题出现,虽然不至于楼房塌陷,但楼房也已经变成了危楼。 从代码和设计的角度来看有: 由不合理的代码所引起的项目无扩展性 数据库经常发生死锁 数据库事务乱用,导致事务占用时间过长 代码容错能力很差,经常因为考虑不足引起事故 程序中打印的大量的无用日志,并且引起性能问题 常用配置信息依然从数据库中读取 滥用线程池,造成栈和堆溢出 从库中查询数据,每次全部查出 业务代码研发不考虑幂等操作 使用缓存不合理,存在惊群效应、缓存穿透等情况 代码上下流流程定义混乱 异常处理机制混乱 再从整体架构角度来看: 整体依然使用单体集群架构 采用单机房服务器布署方式 采用 Nginx+hessian 的方式实现服务化 业务架构划分不彻底,边界模糊 项目拆分不彻底,一个 […]
View DetailsDubbo 出生于阿里系,是阿里巴巴服务化治理的核心框架,并被广泛应用于中国各互联网公司;只需要通过 Spring 配置的方式即可完成服务化,对于应用无入侵,设计的目的还是服务于自身的业务为主。 微服务架构是互联网很热门的话题,是互联网技术发展的必然结果。它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。 虽然微服务架构没有公认的技术标准和规范或者草案,但业界已经有一些很有影响力的开源微服务架构框架提供了微服务的关键思路,例如 Dubbo 和 Spring Cloud。 各大互联网公司也有自研的微服务框架,但其模式都与这二者相差不大。 微服务主要的优势 降低复杂度 将原来耦合在一起的复杂业务拆分为单个服务,规避了原本复杂度无止境的积累。 每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界;每个服务开发者只专注服务本身,通过使用缓存、DAL 等各种技术手段来提升系统的性能,而对于消费方来说完全透明。 可独立部署 由于微服务具备独立的运行进程,所以每个微服务可以独立部署。当业务迭代时只需要发布相关服务的迭代即可,降低了测试的工作量同时也降低了服务发布的风险。 容错 在微服务架构下,当某一组件发生故障时,故障会被隔离在单个服务中。比如通过限流、熔断等方式降低错误导致的危害,保障核心业务正常运行。 扩展 单块架构应用也可以实现横向扩展,就是将整个应用完整的复制到不同的节点。 当应用的不同组件在扩展需求上存在差异时,微服务架构便体现出其灵活性,因为每个服务可以根据实际需求独立进行扩展。 本文主要围绕微服务的技术选型、通讯协议、服务依赖模式、开始模式、运行模式等几方面来综合比较 Dubbo 和 Spring Cloud 这 2 种开发框架。 架构师可以根据公司的技术实力并结合项目的特点来选择某个合适的微服务架构平台,以此稳妥地实施项目的微服务化改造或开发进程。 核心部件 微服务的核心要素在于服务的发现、注册、路由、熔断、降级、分布式配置,基于上述几种必要条件对 Dubbo 和 Spring Cloud 做出对比。 总体架构 Dubbo 核心部件(如下图): Provider:暴露服务的提供方,可以通过 jar 或者容器的方式启动服务。 Consumer:调用远程服务的服务消费方。 Registry:服务注册中心和发现中心。 Monitor:统计服务和调用次数,调用时间监控中心。(Dubbo 的控制台页面中可以显示,目前只有一个简单版本。) Container:服务运行的容器。 Dubbo 总体架构 Spring Cloud总体架构(如下图): Service Provider: 暴露服务的提供方。 Service Consumer:调用远程服务的服务消费方。 EureKa Server: 服务注册中心和服务发现中心。 Spring Cloud 总体架构 点评:从整体架构上来看,二者模式接近,都需要服务提供方,注册中心,服务消费方。 微服务架构核心要素 Dubbo 只是实现了服务治理,而 Spring Cloud 子项目分别覆盖了微服务架构下的众多部件,服务治理只是其中的一个方面。 Dubbo 提供了各种 Filter,对于上述中“无”的要素,可以通过扩展 Filter 来完善。例如: 分布式配置:可以使用淘宝的 diamond、百度的 disconf 来实现分布式配置管理。 服务跟踪:可以使用京东开源的 Hydra,或者扩展 Filter 用 Zippin 来做服务跟踪。 批量任务:可以使用当当开源的 Elastic-Job、tbschedule。 点评:从核心要素来看,Spring […]
View Details0x00 起因 最近需要一个在线编辑器,之前听人说过百度的UEditor不错,去官网下了一个。不过服务端只有ASP.NET版的,如果是为了能尽快使用,只要把ASP.NET版的服务端作为应用部署在IIS上就可以立即使用了。不过我的需求并不急,所以把ASP.NET移植到了ASP.NET Core上。整个过程很简单,只是重新引用了一些包,修改了几处代码,另外就是把Controller中比较长的一个switch语句块重构为了字典,根据url中的action参数从字典中找出并调用相应的Action处理,这样的好处就是如果要扩展action支持的操作无需修改源代码,只要扩展字典就可以,对扩展开放,对修改关闭。最后把服务端功能打成了nuget包UEditorNetCore,方便使用。这篇博客主要就介绍下如何使用UEditorNetCore快速实现UEditor服务端,也可以直接使用源代码中的示例,希望对有这方面需求的园友有所帮助。 0x01 总体设计 当接收到action后,UEditorService会从UEditorActionCollection中找到这个action对应的方法并调用,同时传入HttpContext参数。这些方法调用基层的服务XxxxHandler完成功能,并把返回内容通过HttpContext.Response.WriteAsync()方法写入。如果要扩展对action的支持,可以扩展UEditorActionCollection,具体方法后面有介绍。 0x02 如何使用UEditorNetCore 1.安装UEditorNetCore
|
1 |
Install-Package UEditorNetCore |
2.在Startup.cs的ConfigureServices方法中添加UEditorNetCore服务
|
1 2 3 4 5 6 7 |
public void ConfigureServices(IServiceCollection services) { //第一个参数为配置文件路径,默认为项目目录下config.json //第二个参数为是否缓存配置文件,默认false services.AddUEditorService() services.AddMvc(); } |
3.添加Controller用于处理来自UEditor的请求
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[Route("api/[controller]")] //配置路由 public class UEditorController : Controller { private UEditorService ue; public UEditorController(UEditorService ue) { this.ue = ue; } public void Do() { ue.DoAction(HttpContext); } } |
4.修改前端配置文件ueditor.config.js serverUrl需要参照第3步Controller中配置的路由,按照上面步骤3中的配置,需要以下配置:
|
1 |
serverUrl:"/api/UEditor" |
这样配置后当前端要获取服务端UEditor配置时就会访问/api/UEditor?action=config。 5.修改服务端配置config.json 上传类的操作需要配置相应的PathFormat和Prefix。示例部署在web根目录,因此Prefix都设置为"/"。使用时要根据具体情况配置。 例如示例中图片上传的配置如下:
|
1 2 |
"imageUrlPrefix": "/", /* 图片访问路径前缀 */ "imagePathFormat": "upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", |
关于PathFormat的详细配置可参照官方文档。 6.添加javascript引用
|
1 2 3 |
<script type="text/javascript" charset="utf-8" src="~/lib/ueditor/ueditor.config.js"></script> <script type="text/javascript" charset="utf-8" src="~/lib/ueditor/ueditor.all.min.js"> </script> <script type="text/javascript" charset="utf-8" src="~/lib/ueditor/lang/zh-cn/zh-cn.js"></script> |
0x03 扩展action UEditor前端和后端交互主要通过在url中给出不同的action参数实现的,例如/api/UEditor?action=config会从服务端获取UEditor配置信息。UEditorNetCore目前支持的有8种action: config 获取服务端配置信息 uploadimage 上传图片 uploadscrawl 上传涂鸦 uploadvideo 上传视频 uploadfile 上传文件 listimage 多图片上传 listfile 多文件上传 catchimage 抓取图片 如果以上action无法满足需求,可以方便的增加、覆盖、移除action。 增加action
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void ConfigureServices(IServiceCollection services) { services.AddUEditorService() .Add("test", context => { context.Response.WriteAsync("from test action"); }) .Add("test2", context => { context.Response.WriteAsync("from test2 action"); }); services.AddMvc(); } |
以上代码增加了名字为test和test2两个action,作为示例仅仅返回了字符串。当访问/api/UEditor?action=test时会返回"from test action"。在扩展action时可以使用Config获取服务端配置,也可以使用已有的Handlers,具体可以参考源代码。 覆盖现有action 上面的Add方法除了添加新action外还可以覆盖现有action。当现有的action可能不符合你的要求,可以Add一个同名的action覆盖现有的。 移除action 如果要移除某个action,可以使用Remove方法。
|
1 2 3 4 5 6 |
public void ConfigureServices(IServiceCollection services) { services.AddUEditorService() .Remove("uploadvideo"); services.AddMvc(); } |
以上代码中的Remove("uploadvideo")方法移除了名为uploadvideo的action。 0x04 相关资源 UEditorNetCore代码和示例:https://github.com/durow/UEditorNetCore UEditor代码:https://github.com/fex-team/ueditor UEditor官网:http://ueditor.baidu.com/website/index.html from:https://www.cnblogs.com/durow/p/6116393.html
View Details前世今生: Dubbo源于阿里的淘宝网开源的分布式的服务架构,致力于提供高性能和透明化的RPC远程服务调用方案,是SOA服务化治理方案的核心框架。淘宝网将其开源之后,得到了很多的拓展和支持(比较出名的有:当当网的扩展版本dubbox,京东的扩展版本jd-hydra等) Dubbox(即Dubbo eXtensions)是当当网Fork基于dubbo2.x的升级版本,兼容原有的dubbox。其中升级了zookeeper和spring版本,并且支持restfull风格的远程调用。。 版本: Dubbo目前已停止更新; Dubbox目前还在更新。 说明:dubbox和dubbo 2.x是兼容的, 没有改变dubbo的任何已有的功能和配置方式(除了升级了Spring之类的版本)。 据说淘宝网dubbo与一个非开源的框架HSF有争执,导致dubbo的团队已经解散了,但是其扩展的版本dubbox却得到不断的发展(升级更新); <!——--升级详情——--|——————-- dubbox-2.8.0:该版本已经在生产环境中使用,主要支持REST风格远程调用、支持Kryo和FST序列化、升级了Spring和Zookeeper客户端、调整了demo应用等等 dubbox-2.8.1:主要支持基于嵌入式tomcat的http-remoting,优化了REST客户端性能,在REST中支持限制服务端接纳的最大HTTP连接数等等 dubbox-2.8.2: 支持REST中的HTTP logging,包括HTTP header的字段和HTTP body中的消息体,方便调试、日志纪录等等 提供辅助类便于REST的中文处理 改变使用@Reference annotation配置时的异常处理方式,即当用annotation配置时,过去dubbo在启动期间不抛出依赖服务找不到的异常,而是在具体调用时抛出NPE,这与用XML配置时的行为不一致。 较大的充实了Dubbo REST的文档 dubbox-2.8.3: 在REST中支持dubbo统一的方式用bean validation annotation作参数校验(沈理) 在RpcContext上支持获取底层协议的Request/Response(沈理) 支持采用Spring的Java Config方式配置dubbo(马金凯) 在Dubbo协议中支持基于Jackson的json序列化(Dylan) 在Spring AOP代理过的对象上支持dubbo annotation配置(Dylan) 修正Dubbo管理界面中没有consumer时出现空指针异常(马金凯) 修正@Reference annotation中protocol设置不起作用的bug(沈理) 修正@Reference annotation放在setter方法上即会出错的bug(Dylan) 详见:https://github.com/dangdangdotcom/dubbox/releases ———/> 嵌入: dubbo:嵌入式Jetty dubbox:基于嵌入式tomcat实现dubbo的 HTTP remoting体系(即dubbo-remoting-http) 对Servlet API的支持: dubbo:2.5 dubbox:升级到3.1 序列化: dubbo: dubbox:基于Dubbo默认的RPC协议添加新的JSON序列化实现; 支持基于Kryo和FST的Java高效序列化实现; Zookeeper注册中心: dubbo:Dubbo提供了Zookeeper注册中心,在整个Dubbo的设计里面充分考虑到了各类用户的需求,一些底层的通讯或者是信息存储都提供有大量的不同的存储方案; dubbox:升级ZooKeeper客户端到最新版本; 使用场景: dubbo:使用Dubbo的RPC调用方式,服务间仍然会存在API强依赖; dubbox:相对于Dubbo支持了REST风格的原创调用(HTTP +JSON/XML); ——————————————————- ——简言之(dubbox基于dubbo的升级): ——————- 支持REST风格远程调用(HTTP + JSON/XML); 支持基于Kryo和FST的Java高效序列化实现; 支持基于Jackson的JSON序列化; 支持基于嵌入式Tomcat的HTTP remoting体系; 升级Spring至3.x; 升级ZooKeeper客户端; 支持完全基于Java代码的Dubbo配置; ————————————————- 附录: Dubbo: 官网首页:http://dubbo.io/ , 官方用户指南: http://dubbo.io/User+Guide-zh.htm可以当做SOA架构的学习资料 Dubbox: dubbox入门:http://www.cnblogs.com/yjmyzz/p/dubbox-demo.html dubbox架构: http://www.cnblogs.com/Javame/p/3632473.html 当当网dubbox学习参考文档:http://dangdangdotcom.github.io/dubbox/ 分布式服务框架 dubbo/dubbox 入门示例:http://www.cnblogs.com/wanghang/p/6298957.html ———————- […]
View Detailsphp7.1以上. mcrypt_generic_open is deprecated 这个错误, 就是因为mcrypt扩展,在php7.1以上被废弃,服务器不设置报错等级的话, 这个错误会被框架拦截,然后报出微信demo里的40007错误, 具体解决方案,所有 mcrypt扩展的代码,全部加上 错误抑制符 ,例如 //使用BASE64对需要解密的字符串进行解码 原来的代码
|
1 2 3 4 5 6 7 8 9 10 |
//使用BASE64对需要解密的字符串进行解码 $ciphertext_dec = base64_decode($encrypted); $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); $iv = substr($this->key, 0, 16); mcrypt_generic_init($module, $this->key, $iv); //解密 $decrypted = mdecrypt_generic($module, $ciphertext_dec); mcrypt_generic_deinit($module); mcrypt_module_close($module); |
修改的代码
|
1 2 3 4 5 6 7 8 9 10 |
//使用BASE64对需要解密的字符串进行解码 $ciphertext_dec = base64_decode($encrypted); @$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); $iv = substr($this->key, 0, 16); @mcrypt_generic_init($module, $this->key, $iv); //解密 @$decrypted = mdecrypt_generic($module, $ciphertext_dec); @mcrypt_generic_deinit($module); @mcrypt_module_close($module); |
然后我的问题就解决了。 我的问题主要出在微信的加解密函数上,遇到的坑有几个 1.文档给的class默认有的是小写, 2.一个文件有多个class得拆开 3.构造函数获取的变量名称不对得修改… 这些用断点调试都可以进行解决 还有一种方案是, 我听我朋友说可以用 openssl代替mcrypt扩展的方案(mcrypt编码,openssl解码), 得重写编写微信demo里的代码,虽然我没成功… from:https://blog.csdn.net/qq_38686693/article/details/81388329
View Details参考地址:http://blog.csdn.net/sanbingyutuoniao123/article/details/71124655 关于“SSL证书问题:无法获取本地颁发者证书”错误。很明显,这适用于发送CURL请求的系统(并且没有服务器接收请求) 1)从https://curl.haxx.se/ca/cacert.pem下载最新的cacert.pem 2)将以下行添加到php.ini(如果这是共享托管,并且您无法访问php.ini,那么可以在public_html中添加到.user.ini) curl.cainfo=/path/to/downloaded/cacert.pem from:https://www.cnblogs.com/haoxuanchen2014/p/8067484.html
View Details##从前台获取文件: 前台代码
|
1 2 3 4 |
<input id='location' type="text" class="controls" disabled>//用来显示选择的文件 <input type="button" id="i-check" value="选择报名表" class="btn btn-sm btn-success" onclick="$('#i-file').click();" style="margin-top: -2px"> <input type="file" id='i-file' name="examfile" accept=".xlsx,.xls" onchange="$('#location').val($('#i-file').val());" style="display: none"> //隐藏的存文件的目录 |
前台获取文件为3个input框: 第一个type=“text”,用来显示获取到文件的路径; 第二个type="button"点击按钮; 第三个type="file"隐藏的获取文件的表单。 流程: 当button 触发click事件,则调用$(’#i-file’).click();触发file,当file类型的input获取到值(文件路径),则change事件将路径值显示在location。 jQuery的 .val() – 设置或返回表单字段的值。 ##后台接收文件: 获取: 通过调用request对象的file()方法获取。
|
1 2 3 4 5 6 7 |
if(request()->isPost()){ $file = request()->file('examfile'); if (!$file) { return $this->error('未选择任何文件','JoinController/joinMultiple'); } } |
移动(上传): 内置的上传只是上传到本地服务器,上传到远程或者第三方平台的话需要自己扩展。
|
1 2 3 4 5 6 7 |
// 移动到框架应用根目录/public/uploads/ 目录下 $file_url = ROOT_PATH . 'public' . DS . 'upload'; //. DS . 'excel' $config = [ 'size' => 2097152, 'ext' => 'xlsx,xls' ]; $info = $file->validate($config)->move($file_url); //2MB |
其中validata()是一个格式验证,传入参数为最大字节size和限定的后缀ext;
|
1 2 3 4 5 6 7 8 9 10 |
/** * 设置上传文件的验证规则 * @param array $rule 验证规则 * @return $this */ public function validate($rule = []) { $this->validate = $rule; return $this; } |
move 方法成功的话返回的是一个 \think\File 对象,你可以对上传后的文件进行后续操作, move方法如果只传入一个路径则默认会在目录下生成时间目录,且文件名为自动生成的。 例如:20180926\bbc624371e90924a8483b5421acc2e43.xlsx 时间目录20180926下的这个文件 如果不想生成时间目录,且用原始文件名,则通过传入其他参数。 原始文件名采用:move($file_url,")
|
1 2 3 4 5 6 7 8 |
/** * 移动文件 * @param string $path 保存路径 * @param string|bool $savename 保存的文件名 默认自动生成 * @param boolean $replace 同名文件是否覆盖 * @return false|SplFileInfo false-失败 否则返回SplFileInfo实例 */ public function move($path, $savename = true, $replace = true) |
后续操作: 对move()方法获取到的返回值$info可以执行: getExtension()获得扩展名(后缀名) getSaveName()域名 getFilename()文件名 好的可以获取文件地址通过:
|
1 |
$url=$info->getSaveName(); |
注意:在文档中查到 getSaveName 方法返回的是图片的服务器文件地址,并不能直接用于图片的URL地址,尤其在 windows平台上必须做转换才能正常显示图片。 所以正确的获取路径的方法为:
|
1 2 |
$url=$info->getSaveName(); $file_url .= "\\" . $url; |
getSaveName()得到的其实就是文件名加后缀名,要得到的file_url为可用的URL地址。则 $file_url .= "\\" . $url其中 **.=**为连接符 ,双斜杠为转义字符,本意为单斜杆。 则将**$file_url**传入到真正的文件文件处理的方法中,这里是import()方法。 上述过程完整代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public function upload() { if(request()->isPost()){ $file = request()->file('examfile'); if (!$file) { return $this->error('未选择任何文件','JoinController/joinMultiple'); } // 移动到框架应用根目录/public/uploads/ 目录下 $file_url = ROOT_PATH . 'public' . DS . 'upload'; //. DS . 'excel' $config = [ 'size' => 2097152, 'ext' => 'xlsx,xls' ]; $info = $file->validate($config)->move($file_url); //2MB if ($info) { $url=$info->getSaveName(); $file_url .= "\\" . $url; try{ chmod($file_url, 0777); }catch(\Exception $e){} $result = $this->import($file_url,$info->getExtension()); try{ //使用文件对象的时候没有完全关闭句柄,导致无法删除该文件。 //解决方法只需要在上传逻辑完成之后清空释放一下变量 会在public/upload下多一些空文件夹 unset($info); unlink($file_url); }catch(\Exception $e){} if (!$result) //为"" { return $this->success('导入成功','JoinController/joinMultiple'); }else { return $this->error($result,'JoinController/joinMultiple'); } } else { // 上传失败获取错误信息 return $this->error($file->getError(),'JoinController/joinMultiple'); } }else { return $this->error("非法请求",'JoinController/joinMultiple'); } } |
##处理文件: 我处理的为Excel文件,经过上述步骤将一个Excel的URL地址,和其扩展名传入了处理函数import()。 一.PHPExcel的安装和引用: 注意在处理之前必须采用使用composer安装PHPExcel。 教程 Git地址 use一下全套服务:
|
1 2 3 |
use PHPExcel; use PHPExcel_IOFactory; use PHPExcel_Cell; |
或者:
|
1 2 3 |
//引入PHPExcel header("content-type:text/html; charset=utf-8"); vendor("phpoffice.phpexcel.Classes.PHPExcel"); |
就完成了引用! 二.开始处理: 1.通过实例化的PHPExcel_IOFactory对象调用createReader()方法获得Reader对象;
|
1 |
$PHPReader = PHPExcel_IOFactory::createReader('Excel5'); //use excel2007 for 2007 format |
2.Reader对象的load方法调用URL地址获取到存放文件内容的PHPExcel;
|
1 |
$PHPExcel = $PHPReader->load($filename); |
3.通过PHPExcel的getActiveSheet方法获取sheet当前工作sheet sheet可以存在多个,通过索引getActiveSheet(0)
|
1 |
$sheet = $PHPExcel->getActiveSheet(0);//获得sheet |
|
1 |
$highestRow = $sheet->getHighestRow(); // 取得共有数据数 |
4.将sheet转化为数组进行处理,则Excel中的内容就变成了一个二维数组,方便处理了。 […]
View Details在开发过程中,经常会遇到导入导出的需求,利用phpexcel类实现起来也是比较容易的,下面,我们一步一步实现 提前将phpexcel类下载,并放在扩展目录中,如图所示 一、Excel导出 导出功能,相对复杂,复杂就复杂在设置导出的excel文件的样式以及内容如何循环赋值,而且导出之后的处理结果,一般是直接在通过浏览器下载到本地,或者发送邮件,在此,以下载到本地为例,下面是源码实现 1、首先是获取待导出的数据,这一步比较简单
|
1 2 3 4 5 6 7 8 9 10 |
//首先获取所有待发货的订单 $where['status'] = 2; $order_model = new OrderModel(); $res = $order_model ->with('OGModel') ->where($where) ->select()->toArray(); if(!$res) $this->error('暂无待发货的记录'); //dump($res);die; $count = count($res); |
2、设置Excel导出文件的样式以及为每个单元格赋值,这一步是导出最重要最复杂的一步,好在我都做了注释
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
1 //引入Excel类 2 $objPHPExcel = new \PHPExcel(); 3 // 设置excel文档的属性 4 $objPHPExcel->getProperties()->setCreator("cyf") 5 ->setLastModifiedBy("cyf Test") 6 ->setTitle("order") 7 ->setSubject("Test1") 8 ->setDescription("Test2") 9 ->setKeywords("Test3") 10 ->setCategory("Test result file"); 11 //设置excel工作表名及文件名 12 $title = '待发货订单'; 13 $excel_filename = '待发货订单_'.date('Ymd_His'); 14 // 操作第一个工作表 15 $objPHPExcel->setActiveSheetIndex(0); 16 //第一行设置内容 17 $objPHPExcel->getActiveSheet()->setCellValue('A1',$excel_filename); 18 //合并 19 $objPHPExcel->getActiveSheet()->mergeCells('A1:AC1'); 20 //设置单元格内容加粗 21 $objPHPExcel->getActiveSheet()->getStyle('A1')->getFont()->setBold(true); 22 //设置单元格内容水平居中 23 $objPHPExcel->getActiveSheet()->getStyle('A1')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER); 24 //设置excel的表头 25 $sheet_title = array('订单编号','下单时间','订单金额','订单状态','支付方式','付款时间','支付单号','配送方式', 26 '收货人','联系电话','省','市','区','地址','运费','商品名称','商品货号','商品规格','商品数量','商品单价', 27 '发货单号','卖家备注','实付金额','微信/支付宝金额','余额支付','优惠金额','买家备注','物流公司','物流单号'); 28 // 设置第一行和第一行的行高 29 $objPHPExcel->getActiveSheet()->getRowDimension('1')->setRowHeight(20); 30 $objPHPExcel->getActiveSheet()->getRowDimension('2')->setRowHeight(25); 31 $letter = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 32 'Q','R','S','T', 'U','V','W','X','Y','Z','AA','AB','AC'); 33 //设置单元格 34 $objPHPExcel->getActiveSheet()->getStyle('A2:AC2')->getBorders()->getAllBorders()->setBorderStyle(\PHPExcel_Style_Border::BORDER_THIN); 35 //首先是赋值表头 36 for ($k=0;$k<29;$k++) { 37 $objPHPExcel->getActiveSheet()->setCellValue($letter[$k].'2',$sheet_title[$k]); 38 $objPHPExcel->getActiveSheet()->getStyle($letter[$k].'2')->getFont()->setSize(10)->setBold(true); 39 //设置单元格内容水平居中 40 $objPHPExcel->getActiveSheet()->getStyle($letter[$k].'2')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER); 41 //设置每一列的宽度 42 $objPHPExcel->getActiveSheet()->getColumnDimension($letter[$k])->setWidth(18); 43 $objPHPExcel->getActiveSheet()->getColumnDimension('P')->setWidth(30); 44 45 } 46 //开始赋值 47 for ($i=0;$i<$count;$i++) { 48 //先确定行 49 $row = $i+3;//再确定列,最顶部占一行,表头占用一行,所以加3 50 $temp = $res[$i]; 51 for ($j = 0;$j<29;$j++) { 52 //开始为每个单元格赋值 53 //初始化地址数据 54 $address_arr = []; 55 $address_arr = explode(',',$temp['address_address']); 56 //初始化商品数据 57 $product_name = $product_number = $product_attr = $product_num = $product_price = ''; 58 $cl = ','.chr(10); 59 foreach ($temp['o_g_model'] as $v) { 60 $product_name .= $v['title'].$cl; 61 $product_number .= $v['attrid'].$cl; 62 $attr = []; 63 $attr = json_decode($v['attr_name'],true); 64 if ($attr) { 65 foreach ($attr as $vv) { 66 $product_attr .= $vv.' '; 67 } 68 $product_attr .= $cl; 69 } 70 $product_num .= $v['num'].$cl; 71 $product_price .= $v['user_price'].$cl; 72 } 73 switch ($j) { 74 case 0 : 75 //订单编号 76 $cellvalue = $temp['order_num']; 77 break; 78 case 1 : 79 //下单时间 80 $cellvalue = date('Y-m-d H:i:s',$temp['addtime']); 81 break; 82 case 2 : 83 //订单金额 84 $cellvalue = $temp['price_sum']; 85 break; 86 case 3 : 87 //订单状态 88 $cellvalue = lang('ORDER_STATUS')[$temp['status']]; 89 break; 90 case 4 : 91 //支付方式 92 $cellvalue = lang('ORDER_PAY')[$temp['pay_type']]; 93 break; 94 case 5 : 95 //付款时间 96 $cellvalue = ''; 97 break; 98 case 6 : 99 //支付单号 100 $cellvalue = ''; 101 break; 102 case 7 : 103 //配送方式 104 $cellvalue = lang('POST_TYPE')[$temp['post_type']]; 105 break; 106 case 8 : 107 //收货人姓名 108 $cellvalue = $temp['address_name']; 109 break; 110 case 9 : 111 //联系电话 112 $cellvalue = $temp['address_phone']; 113 break; 114 case 10 : 115 //省 116 $cellvalue = $address_arr[0]; 117 break; 118 case 11 : 119 //市 120 $cellvalue = $address_arr[1]; 121 break; 122 case 12 : 123 //区 124 $cellvalue = $address_arr[2]; 125 break; 126 case 13 : 127 //地址 128 $cellvalue = $address_arr[3]; 129 break; 130 case 14 : 131 //运费 132 $cellvalue = $temp['p_price']; 133 break; 134 case 15 : 135 //商品名称 136 $cellvalue = $product_name; 137 break; 138 case 16 : 139 //商品货号 140 $cellvalue = $product_number; 141 break; 142 case 17 : 143 //商品规格 144 $cellvalue = $product_attr; 145 break; 146 case 18 : 147 //商品数量 148 $cellvalue = $product_num; 149 break; 150 case 19 : 151 //商品单价 152 $cellvalue = $product_price; 153 break; 154 case 20 : 155 //发货单号 156 $cellvalue = '已废弃'; 157 break; 158 case 21 : 159 //卖家备注 160 $cellvalue = ''; 161 break; 162 case 22 : 163 //实付金额 164 $cellvalue = $temp['pay_money']; 165 break; 166 case 23 : 167 //微信支付宝金额 168 $cellvalue = $temp['pay_money']; 169 break; 170 case 24 : 171 //余额支付 172 $cellvalue = $temp['static_money']; 173 break; 174 case 25 : 175 //优惠金额 176 $cellvalue = $temp['coupon']; 177 break; 178 case 26 : 179 //买家备注 180 $cellvalue = ''; 181 break; 182 case 27 : 183 //物流公司 184 $cellvalue = ''; 185 break; 186 case 28 : 187 //物流单号 188 $cellvalue = ''; 189 break; 190 } 191 //赋值 192 $objPHPExcel->getActiveSheet()->setCellValue($letter[$j].$row, $cellvalue); 193 //设置字体大小 194 $objPHPExcel->getActiveSheet()->getStyle($letter[$j].$row)->getFont()->setSize(10); 195 //设置单元格内容水平居中 196 $objPHPExcel->getActiveSheet()->getStyle($letter[$j].$row)->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER); 197 //设置自动换行 198 if ((in_array($j,[15,16,17,18,19])) && "" != $cellvalue) { 199 $objPHPExcel->getActiveSheet()->getStyle($letter[$j].$row)->getAlignment()->setWrapText(true); // 自动换行 200 $objPHPExcel->getActiveSheet()->getStyle($letter[$j].$row)->getAlignment()->setVertical(\PHPExcel_Style_Alignment::VERTICAL_CENTER); // 垂直方向上中间居中 201 } 202 } 203 // 设置行高 204 $objPHPExcel->getActiveSheet()->getRowDimension($row)->setRowHeight(21); 205 } 206 unset($res); |
3、设置完之后,将生成的excel文件,通过浏览器下载到本地
|
1 2 3 4 5 6 7 |
1 //赋值结束,开始输出 2 $objPHPExcel->getActiveSheet()->setTitle($title); 3 header('Content-Type: application/vnd.ms-excel'); 4 header('Content-Disposition: attachment;filename="'.$excel_filename.'.xls"'); 5 header('Cache-Control: max-age=0'); 6 $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5'); 7 $objWriter->save('php://output'); |
4、也可以在服务器上生成excel文件,然后发送到用户邮箱
|
1 2 3 |
1 $filename = __ROOT__.'/upload/files/'.$excel_filename; 2 $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5'); 3 $objWriter->save($filename); |
二、Excel导入 相比导出,导入就简单多了,废话不多说,直接上源码 1、利用form表单获取上传的excel文件
|
1 2 3 4 5 6 7 |
1 $file = $_FILES['file']; 2 if ($file['error'] == 4) $this->error('请选择上传excel文件'); 3 $file_types = explode ( ".", $file['name'] ); 4 $excel_type = array('xls','csv','xlsx'); 5 if (!in_array(strtolower(end($file_types)),$excel_type)){ 6 $this->error("不是Excel文件,请重新上传"); 7 } |
2、然后,读取excel文件里面的数据,这里是边读取边处理,或者全部读取出来再处理
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
1 //设置获取excel对象 2 $objReader = \PHPExcel_IOFactory::createReader('Excel5');//配置成2003版本,因为office版本可以向下兼容 3 $objPHPExcel = $objReader->load($file['tmp_name'],$encode='utf-8');//$file 为解读的excel文件 4 //dump($objPHPExcel);die; 5 $sheet = $objPHPExcel->getSheet(0); 6 $highestRow = $sheet->getHighestRow(); // 取得总行数 7 $success_item = $fail_item = 0; 8 //发货 9 $order_model = new OrderModel(); 10 //开始读取数据 11 for($j=3;$j<=$highestRow;$j++) 12 { 13 $order_num = $objPHPExcel->getActiveSheet()->getCell('A'.$j)->getValue(); 14 $poster = $objPHPExcel->getActiveSheet()->getCell("AB".$j)->getValue();//物流公司 15 $logistics = $objPHPExcel->getActiveSheet()->getCell("AC".$j)->getValue();//物流单号 16 $comm = $objPHPExcel->getActiveSheet()->getCell("V".$j)->getValue();//卖家备注 17 //判断条件 18 if(!is_null($order_num) && $order_num){ 19 $res = $order_model->where('order_num',$order_num)->field('itemid,status')->find(); 20 if ($res && $res['status'] == 2) { 21 if ((!is_null($poster) && $poster) || (!is_null($logistics) && $logistics)) { 22 //更改状态 23 OrderPlanModel::addLog($res['itemid'],'确认发货',$comm); 24 $order_model->Update([ 25 'status'=>3, 26 'poster'=>$poster, 27 'logistics'=>$logistics, 28 ],['itemid'=>$res['itemid']]); 29 $success_item ++; 30 } else { 31 $fail_item ++; 32 } 33 } else { 34 $fail_item ++; 35 } 36 } else { 37 $fail_item++; 38 } 39 } 40 $this->success('成功条数:'.$success_item.',失败条数:'.$fail_item); |
多学、 多记、 多练、 from:https://www.cnblogs.com/cyfblogs/p/10115541.html
View Details在之前有写过一篇文章讲述了使用PHP快速生成excel表格文件并下载,这种方式生成Excel文件,生成速度很快,但是有缺点是: 1.单纯的生成Excel文件,生成的文件没有样式,单元格属性(填充色,宽度,高度,边框颜色…)不能自定义; 2.生成的文件虽然可以打开,但是兼容性很差,每次打开,都会报一个警告: 今天使用一个第三方的SDK(PHPExcel)实现Excel文件的导入和导出。 准备工作: 1.下载PHPExcel的SDK,下载地址:https://github.com/PHPOffice/PHPExcel。 2.将SDK解压之后的Class文件拷贝到自己的项目。 一.Excel文件读取
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
function excelToArray(){ require_once dirname(__FILE__) . '/Lib/Classes/PHPExcel/IOFactory.php'; //加载excel文件 $filename = dirname(__FILE__).'/result.xlsx'; $objPHPExcelReader = PHPExcel_IOFactory::load($filename); $sheet = $objPHPExcelReader->getSheet(0); // 读取第一个工作表(编号从 0 开始) $highestRow = $sheet->getHighestRow(); // 取得总行数 $highestColumn = $sheet->getHighestColumn(); // 取得总列数 $arr = array('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); // 一次读取一列 $res_arr = array(); for ($row = 2; $row <= $highestRow; $row++) { $row_arr = array(); for ($column = 0; $arr[$column] != 'F'; $column++) { $val = $sheet->getCellByColumnAndRow($column, $row)->getValue(); $row_arr[] = $val; } $res_arr[] = $row_arr; } return $res_arr; } |
或者:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
function excelToArray(){ require_once dirname(__FILE__) . '/Lib/Classes/PHPExcel/IOFactory.php'; //加载excel文件 $filename = dirname(__FILE__).'/result.xlsx'; $objPHPExcelReader = PHPExcel_IOFactory::load($filename); $reader = $objPHPExcelReader->getWorksheetIterator(); //循环读取sheet foreach($reader as $sheet) { //读取表内容 $content = $sheet->getRowIterator(); //逐行处理 $res_arr = array(); foreach($content as $key => $items) { $rows = $items->getRowIndex(); //行 $columns = $items->getCellIterator(); //列 $row_arr = array(); //确定从哪一行开始读取 if($rows < 2){ continue; } //逐列读取 foreach($columns as $head => $cell) { //获取cell中数据 $data = $cell->getValue(); $row_arr[] = $data; } $res_arr[] = $row_arr; } } return $res_arr; } |
|
1 |
两种方法均可将表格数据转化为数组,然后哦再进行相关的操作就简单了。 二.Excel文件导出
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/** * 创建(导出)Excel数据表格 * @param array $list 要导出的数组格式的数据 * @param string $filename 导出的Excel表格数据表的文件名 * @param array $indexKey $list数组中与Excel表格表头$header中每个项目对应的字段的名字(key值) * @param array $startRow 第一条数据在Excel表格中起始行 * @param [bool] $excel2007 是否生成Excel2007(.xlsx)以上兼容的数据表 * 比如: $indexKey与$list数组对应关系如下: * $indexKey = array('id','username','sex','age'); * $list = array(array('id'=>1,'username'=>'YQJ','sex'=>'男','age'=>24)); */ function exportExcel($list,$filename,$indexKey,$startRow=1,$excel2007=false){ //文件引入 require_once APP_ROOT.'/Api/excel/PHPExcel.php'; require_once APP_ROOT.'/Api/excel/PHPExcel/Writer/Excel2007.php'; if(empty($filename)) $filename = time(); if( !is_array($indexKey)) return false; $header_arr = array('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); //初始化PHPExcel() $objPHPExcel = new PHPExcel(); //设置保存版本格式 if($excel2007){ $objWriter = new PHPExcel_Writer_Excel2007($objPHPExcel); $filename = $filename.'.xlsx'; }else{ $objWriter = new PHPExcel_Writer_Excel5($objPHPExcel); $filename = $filename.'.xls'; } //接下来就是写数据到表格里面去 $objActSheet = $objPHPExcel->getActiveSheet(); //$startRow = 1; foreach ($list as $row) { foreach ($indexKey as $key => $value){ //这里是设置单元格的内容 $objActSheet->setCellValue($header_arr[$key].$startRow,$row[$value]); } $startRow++; } // 下载这个表格,在浏览器输出 header("Pragma: public"); header("Expires: 0"); header("Cache-Control:must-revalidate, post-check=0, pre-check=0"); header("Content-Type:application/force-download"); header("Content-Type:application/vnd.ms-execl"); header("Content-Type:application/octet-stream"); header("Content-Type:application/download");; header('Content-Disposition:attachment;filename='.$filename.''); header("Content-Transfer-Encoding:binary"); $objWriter->save('php://output'); } |
导出文件,还可以设置模板
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
function exportExcel($list, $filename, $indexKey = array()) { require_once dirname(__FILE__) . '/Lib/Classes/PHPExcel/IOFactory.php'; require_once dirname(__FILE__) . '/Lib/Classes/PHPExcel.php'; require_once dirname(__FILE__) . '/Lib/Classes/PHPExcel/Writer/Excel2007.php'; $header_arr = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'); //$objPHPExcel = new PHPExcel(); //初始化PHPExcel(),不使用模板 $template = dirname(__FILE__) . '/template.xls'; //使用模板 $objPHPExcel = PHPExcel_IOFactory::load($template); //加载excel文件,设置模板 $objWriter = new PHPExcel_Writer_Excel5($objPHPExcel); //设置保存版本格式 //接下来就是写数据到表格里面去 $objActSheet = $objPHPExcel->getActiveSheet(); $objActSheet->setCellValue('A2', "活动名称:江南极客"); $objActSheet->setCellValue('C2', "导出时间:" . date('Y-m-d H:i:s')); $i = 4; foreach ($list as $row) { foreach ($indexKey as $key => $value) { //这里是设置单元格的内容 $objActSheet->setCellValue($header_arr[$key] . $i, $row[$value]); } $i++; } // 1.保存至本地Excel表格 //$objWriter->save($filename.'.xls'); // 2.接下来当然是下载这个表格了,在浏览器输出就好了 header("Pragma: public"); header("Expires: 0"); header("Cache-Control:must-revalidate, post-check=0, pre-check=0"); header("Content-Type:application/force-download"); header("Content-Type:application/vnd.ms-execl"); header("Content-Type:application/octet-stream"); header("Content-Type:application/download");; header('Content-Disposition:attachment;filename="' . $filename . '.xls"'); header("Content-Transfer-Encoding:binary"); $objWriter->save('php://output'); } |
先弄一个excel模板 然后使用该模板导出数据 from:https://www.jianshu.com/p/f7c07763a16e
View DetailsSpring Boot 内嵌容器Undertow参数设置 配置项:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程 # 不要设置过大,如果过大,启动项目会报错:打开文件数过多 server.undertow.io-threads=16 # 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线程 # 它的值设置取决于系统线程执行任务的阻塞系数,默认值是IO线程数*8 server.undertow.worker-threads=256 # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理 # 每块buffer的空间大小,越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可 server.undertow.buffer-size=1024 # 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region server.undertow.buffers-per-region=1024 # 是否分配的直接内存(NIO直接分配的堆外内存) server.undertow.direct-buffers=true |
来看看源代码: https://github.com/undertow-io/undertow/blob/master/core/src/main/java/io/undertow/Undertow.java
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
ioThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2); workerThreads = ioThreads * 8; //smaller than 64mb of ram we use 512b buffers if (maxMemory < 64 * 1024 * 1024) { //use 512b buffers directBuffers = false; bufferSize = 512; } else if (maxMemory < 128 * 1024 * 1024) { //use 1k buffers directBuffers = true; bufferSize = 1024; } else { //use 16k buffers for best performance //as 16k is generally the max amount of data that can be sent in a single write() call directBuffers = true; bufferSize = 1024 * 16 - 20; //the 20 is to allow some space for protocol headers, see UNDERTOW-1209 } |
很显然,Undertow认为它的运用场景是在IO密集型的系统应用中,并且认为多核机器是一个比较容易满足的点,Undertow初始化假想应用的阻塞系数在0.8~0.9之间,所以阻塞线程数直接乘了个8,当然,如果对应用较精确的估测阻塞系数,可以配置上去。 Spring Boot内嵌容器支持Tomcat、Jetty、Undertow。为什么选择Undertow? 这里有一篇文章,时间 2017年1月26日发布的: Tomcat vs. Jetty vs. Undertow: Comparison of Spring Boot Embedded Servlet Containers 1. Setup Spring Boot Application We will use Maven to setup a new project in Eclipse with the appropriate dependencies. We will use the starter parent for this example but the dependencies in a production application will likely be altered to streamline, optimize or customize. 1.1 Setup Spring Boot Dependencies The default embedded servlet container is Tomcat. This version of Spring Web […]
View Details