关于后端:从业务开发中学习和理解架构设计

3次阅读

共计 9927 个字符,预计需要花费 25 分钟才能阅读完成。

简介:在设计代码目录划分计划的过程中,看了一些工程结构设计的材料,读了一些对于架构设计的书,对于架构有了一些了解。本文是对这段学习和工作实现过程的思考和积淀。心愿可能解答“架构到底是什么?架构和业务之间的关系?”,“好的架构的设计出发点是什么?好的架构应该是什么样的?”这几个问题。

作者 | 张东爱 (当爱) 起源 | 阿里开发者公众号前言在软件开发畛域常常会接触到架构这个词汇,在我最后的印象中,架构是一个很高级的词汇。它仿佛代表了简单的工程构造、高层次的形象设计、最新的开发语言个性等等。对于过后只专一于写业务逻辑的我来说,未免心生对架构的敬畏。工作中对架构的探讨很少,呈现则是一些高级艰涩的形容,然而素来没有人分明地解释过架构做了哪些事。所以,架构到底是什么?架构和业务之间是什么关系?当咱们看一些对于架构的书籍或者材料,未免会接触到一些对架构的定义或者形容。比方:束缚、规定、边界、实体关系、模型定义等等。然而懂得这些概念并不能帮忙咱们设计进去更好的架构,当咱们套用设计准则进行架构设计时,未免会感觉空洞乏味,总感觉少了点什么。尽管咱们为架构设计做了很多事,然而仿佛什么也没做。因为只针对架构设计自身来说,很难说分明它所产生的价值。所以,好的架构设计的出发点是什么?好的架构应该是什么样的呢?去年我有一个工作:将咱们以后工程的代码进行从新的拆分和组合,以厘清模块间的关系,控制工程中模块依赖的复杂度。这看起来是一个很简略的工作,找到一个不同于以后的且更正当的目录划分计划,就能够尝试落地施行。然而这又是一个很艰难的工作,因为咱们首先要答复有哪些模块、模块间是什么依赖关系的问题。其实,回到工作的自身,咱们并不是只想对代码文件进行从新的组织和划分,咱们的指标是业务模块解耦合,定义并明确业务模块间的依赖规定。面对这样的指标,咱们须要首先从业务视角更清晰地定义和划分模块,而后从工程构造视角确定模块间的关系。所以,代码目录调整实际上是一个对业务场景、工程构造了解和设计的问题。代码目录的构造代表了咱们的工程构造,也是业务场景划分的形象形容,更是模块定义以及模块依赖关系的展示。在设计代码目录划分计划的过程中,看了一些工程结构设计的材料,读了一些对于架构设计的书。对于架构有了一些了解。本文是对这段学习和工作实现过程的思考和积淀。我心愿可能答复下面提到的几个问题:架构到底是什么?架构和业务之间的关系好的架构的设计出发点是什么?好的架构应该是什么样的什么是架构架构的定义首先架构是一个汉语词汇。它的定义是:人们对一个构造内的元素及元素间关系的一种主观映射的产物。从这个定义能够看出,传统的架构在形容一个零碎中有什么元素,以及元素之间关系。在修建畛域,架构也用于形容建筑物的构造。作为一个计算机领域的词汇,架构的定义是:无关软件整体构造与组件的形象形容,用于领导大型软件系统各个方面的设计。实际上也在定义有什么以及关系的问题。

从工程化解读架构设计的作用无论是在修建畛域还是计算机领域,咱们通常会用工程形容这类工作的我的项目。比方我所在的部门是工程技术核心,我是一个工程类的程序员等。咱们能够称之为工程的工作我的项目包含:建筑工程、军事工程、水利工程、生物工程、软件工程等。而咱们在实现我的项目的过程中,进行架构设计实际上就是推动施行工程化的一部分。那么进行工程架构设计会思考哪些因素,它对施行工程化的作用是什么呢?假如,读者你当初是一位修建工程师,负责建造一栋屋宇。尽管咱们没有真正的盖过房子,然而在进行屋宇的整体结构设计时,你肯定会关怀这些:屋宇用处。首先要明确这栋房子是干什么用的屋宇层数。和用处严密相干,不同用处的房子层数也是不一样的屋宇外观。定义这栋屋宇应该长什么样屋宇的布局。定义这栋屋宇应该怎么更好地被应用等等。咱们称下面这几个属性是屋宇的根底能力。作为一个靠谱的修建工程师,你肯定还会着重地设计这些:水电走向。这很重要。保障屋宇的安全性和应用的便捷承重和抗压。屋宇的使用寿命很大水平上依赖于此等等。咱们称下面的这几个属性是安全性和性能。另外一方面,你大略不会关怀屋宇的装修格调、地板色彩、衣柜品牌等等因素。咱们称这些为利用细节。总结来说,进行屋宇的工程架构设计时更多地关系底层设计,而不在乎过多的技术细节。所以,咱们能够给架构的作用下一个定义:在明确用处的根底上定义应用的规定和束缚,提供了根底的撑持能力,并保障安全性、性能和应用周期。软件架构设计的准则和要求到目前为止,咱们曾经明确了在做架构设计时必须遵循的前提和准则:明确用处。此外也对架构设计提出要求:提供根底能力、保障安全性、性能等。同样的,引申到计算机领域。当咱们进行软件架构设计时也必须遵循的准则有:1、架构设计肯定要从业务场景登程这实际上就是明确用处的大前提。架构设计肯定是要从业务登程、面向业务变动的。只有在咱们明确了咱们的业务场景和业务指标后,在此基础上进行的架构设计才是能真正产生业务价值的。一个脱离了业务场景而设计的架构,无论如许新鲜和高级,也绝不是一个好的架构。2、架构设计肯定要落到业务场景中去验证咱们不能只从根底能力、安全性或者性能方面去评判一个架构的好坏。架构对业务开发的反对能力,面向业务变动时的灵便度以及继续演进能力等都是评判的因素。此外,咱们要求软件架构必须是灵便的,可能满足将来业务继续倒退的要求。业务场景是一直变动的,架构也要具备追随业务状态一直演进的能力。架构设计的外围是保障面向业务变动时有足够灵便的响应力,这要求架构设计可能辨认到业务的外围畛域。所以,无论是面向以后还是面向未来,架构设计都须要真正地辨认和了解业务问题。架构设计的准则本章节介绍几个软件架构设计时能够遵循的准则,实际上在进行功能模块设计也能够参考这些设计准则。SRP 繁多职责准则一个函数只负责实现一个性能任何一个模块只对某一类行为者负责一个类或者函数应该有且仅有一个被扭转的理由在理论的编码中,咱们还是能够看到很多违反繁多职责的例子的,比方超长的函数体。一个函数内做了很多事,实际上就是负责了太多的性能,很多的变更都要批改这个函数,这导致很难管制变更影响的范畴。咱们能够将大函数拆分成小函数,小函数体负责的性能更加繁多,相应的也会更加灵便。所以咱们倡议大家多写一些小的函数体。然而不要在函数拆分的过程中进行适度的封装和形象。OCP 开闭准则易于扩大,抗拒批改模块要易于扩大,管制批改。这是咱们在初学编程语言时就会被教育到的设计准则。开闭准则帮忙咱们设计更加灵便的模块,同时还能管制模块变更的影响范畴。LSP 里氏替换准则所有援用父类的中央都能够替换成子类,而行为不产生扭转应用里氏替换准则能够保障父类的复用性。它次要是用来判断形象和继承关系设计是否正当,即某个类是否应该具备某个属性,以及一个类到底是不是另外一个类的子类。举一个典型的例子,乘马是乘马,乘白马也是乘马,乘黑马也是乘马。那么白马和黑马就是马的子类,是合乎 LSP 的。上面是两个典型的违反 LSP 准则的例子。也是网上也特地常见的例子。第一个是正方形不是矩形。class Rectangle {
public:

 int32_t getWidth() const {return width;}
 int32_t getHeight() const {return height;}

 virtual void setWidth(int32_t w) {width = w;}
 virtual void setHeight(int32_t h) {height = h;}

private:

int32_t width = 0;
int32_t height = 0;

};
class Square : public Rectangle {
public:

 void setWidth(int32_t w) override {Rectangle::setWidth(w);
     Rectangle::setHeight(w);
 }
 void setHeight(int32_t h) override {// …}

}; void reSize(Rectangle rect) {

 while (rect.getHeight() <= rect.getWidth()){rect.setHeight(rect.getWidth() + 1);
}

}正方形类 Square 继承自矩形类 Rectangle,并且重写了函数 setWidth 和 setHeight。在函数 reSize 中,将父类 Rectangle 对象替换成子类 Square 后,将会呈现死循环,程序出现异常。不合乎 LSP 准则。所以正方形不是矩形。第二个是鸵鸟不是鸟。class Bird {
public:

 int32_t getVelocity() const {return velocity;}

private:

 int32_t velocity = 0; // 飞行速度

};

class Ostrich : public Bird {
};void crossRiver(Bird bird) {

 int32_t distance = 1000;
 int32_t elapsed = distance / bird.getVelocity();

}鸟类 Brid 具备飞行速度的属性,鸵鸟类 Ostrich 继承自类 Brid,飞行速度默认为 0。在函数 crossRiver 中,将基类 Brid 对象替换成子类 Ostrich 对象后,获取的飞行速度为 0,呈现了除 0 异样。不合乎 LSP 准则。所以鸵鸟不是鸟。在这两个例子中,联合里氏替换准则,咱们得出了两个奇怪的论断,违反了几何学和生物学的常识。其实问题在于咱们对形象和接口的设计上。比方前一个例子中 reSize 函数,它的条件判断是有问题的。对于一个矩形对象,宽高不肯定非得相等,所以将宽高相等作为循环的条件是不合理的。对于后一个例子,航行并不是鸟类的对立特色,所以形象的鸟类不应该领有飞行速度这个属性,也不应该具备航行的接口。那么咱们应该怎么解决这个问题呢。精确来说,鸟类能够具备是否能够航行的接口,而后有一个速度属性。能够航行的鸟返回飞行速度,而鸵鸟返回行走速度。所以,里氏替换准则用于验证咱们的接口和形象设计是否正当,同时也能够验证继承关系是否正当。ISP 接口隔离准则不依赖于本人不须要的货色应用接口类的形式细化功能模块,每个接口类负责某一类明确的性能领导咱们进行接口设计的准则。相似于繁多职责准则,多个繁多的接口负责的性能更简略,更易于保护,这比一个宏大的接口要好。在做接口设计时要尽量保障接口的玲珑、简洁和正交,这样给业务层提供了更多的灵活性。一个大的接口可能会做业务层并不心愿做的事,同时当业务层须要扩大性能时也会使变更影响的范畴过大。DIP 依赖反转准则(依赖倒置)为了保证系统的灵活性(易于批改)和稳定性(批改影响范畴小),在依赖关系中应该防止援用具体的类接口比实现更稳固,所以尽量避免批改函数实现时对依赖该接口的模块的影响继承关系是依赖关系中最强的,尽量避免继承自有具体实现的类这个准则目标在于升高使模块间的耦合度,并且使底层模块更易于被批改和替换。当下层性能发生变化时能够管制对下层业务的影响范畴,使得整体零碎更加稳固和灵便。DIP 准则在前面章节介绍架构设计办法时也会屡次提到。以上这五个设计准则统称为 SOLID 准则。在《整洁架构之道》中有比拟具体的介绍。奥卡姆剃刀准则奥卡姆剃刀准则不是在软件开发畛域提出的,而是在哲学领域提出的。奥卡姆剃刀准则对迷信和哲学的倒退都极为重要,因为它通知人们实践应该尽量简洁,实践中所有不影响论断的多余局部都应该被剔除掉。正如奥卡姆剃刀准则的精华一样,它的形容十分简洁无力:如非必要,勿增实体。咱们也能够称它为简略即为美准则。艰深的形容是:用尽量少的步骤实现一件事。或者,如果对于一个事物有两种解释,采纳最简略或能被证伪的那种。正是因为奥卡姆剃刀准则,咱们才更加置信哥白尼的日心说,更置信牛顿和爱因斯坦。否则,地球是宇宙核心的实践也没错,只是其余行星和恒星盘绕地球的轨道公式也太简单了,而且也容易被天然景象证伪。在泛滥的介绍软件设计办法的书籍和材料中也屡次提到过奥卡姆剃刀准则。利用到软件开发畛域,它的确给了咱们很大的启发。构想一下咱们是不是遇到过这样的场景:费劲地向他人解释某个模块为什么那么设计为某段代码加的正文比代码都多为了解决一个问题而引入一个新的模块当咱们费劲阐明和解释某个代码设计时,真正的问题并不在于咱们解释的不够充沛,或者听众不够聪慧了解不了,而在于代码设计自身没有很好地体现其业务语义。实际上过多的解释和正文都是多余的,是能够被奥卡姆剃刀砍掉的。对于为了解决一个问题而引入一个模块也是在工作中常常遇到的问题。有很多起因导致某些模块变得腐化难以保护,比方最后的设计没有很好地贴合业务场景;编码标准不够好,前面的批改也没有遵守规则;接手者没有齐全了解作者的用意就着手批改等等。而程序员也常常会有的一个想法是:当一个模块难以保护了,最好的办法是用一个新模块替换掉它。实际上这种办法并没有涉及问题的实质,在没有找到导致模块腐化的起因之前,在没有制订标准的模块设计方案之前,咱们都不能保障新模块不会有旧模块一样的问题。所以,想开发新模块替换掉旧模块很大水平上是在回避对旧模块问题的思考,新模块也很有可能沦落到旧模块一样的境地。如果答复不了这个矛盾的问题,还是用奥卡姆剃刀把新模块剔除掉吧,新模块是多余的,并没有解决真正的问题。奥卡姆剃刀准则保障解决问题的办法是简略无效的,同时也束缚咱们该当思考更基本的问题,不能浮于问题表象采纳最省力的办法。其余的设计准则概览 DRY(Dont Repeat Yourself):保障代码的可复用性,防止代码逻辑的反复 YAGNI(You Aint Gonna Need It):代码应易于扩大,但要防止适度设计,不要编写以后用不到的代码。KISS(Keep It Simple, Stupid):把事件想简单,做简略 POLA(Principle of Least Astonishment):最小惊奇准则。代码应合乎逻辑和标准,给阅读者起码的惊吓。接口设计防止别树一帜。罕用的几种架构设计分层架构分层架构是指基于具体的业务模型依照功能模块将代码进行分层组织。每一层代表了一组相干性能的汇合。具体分为几层没有明确的规定,通常能够分为 3 - 4 层或者更多。在分层架构中,依赖关系是由上往下,下层依赖于上层,不能反向依赖。越往下的档次越通用,偏差于根底能力。越往上档次越动静,偏差于业务。

 分层架构设计依照依赖规定的严格水平分为严格型分层架构和松散型分层架构。严格型分层架构要求每一层只能拜访其间接依赖的层,不能拜访其间接依赖的层。松散型分层架构容许每一层拜访位于其下方的任意一层。严格型分层架构使得各个层之间的耦合度降到最低,然而灵活性有余,当下层须要拜访上面间接层的能力时必须从上往下层层穿透。松散型分层架构在保障依赖规定的前提下提供了足够的灵活性,所以大部分分层架构都是松散型的。分层架构设计简洁易懂。对形象事物依照根底特色进行分类,合乎咱们的思维习惯,易于了解。分层架构设计保障每一层外部有较好的内聚性,缩小了层与层之间的耦合度,易于根底能力的积淀和复用,也易于控制变更带来的危险。另外一方面,分层架构设计尽管定义了多个层,然而层与层之间的边界并不是特地清晰。对于新增的模块有可能难以确定应该放在哪一层。或者随着业务逻辑的变动,将来可能须要调整模块所属的档次。分层架构中,下层模块对上层模块有间接的依赖,上层模块的实现间接向下层模块裸露。在批改或者替换上层模块时须要批改下层模块,对下层业务的影响较大。业务实现与根底能力没有齐全解耦合。六边形架构又称为端口 - 适配器架构。为了解决具体实现依赖于根底能力的问题,采纳依赖倒置设计办法将工程分为外部和内部。外部是具体的业务逻辑,内部是依赖的根底能力。外部业务逻辑不再间接依赖于内部根底能力,而是都依赖于其形象定义。应用依赖注入的形式将内部实现传入外部业务逻辑中。外部和内部应用接口进行交互,外部业务逻辑拜访根底能力时间接调用其形象接口即可。

 六边形架构解决了业务逻辑间接依赖内部模块的问题,它们都依赖于形象,不依赖于间接的实现和细节。它们间接通过定义好的接口进行交互。因为业务逻辑和内部模块没有间接的依赖关系,在批改和替换内部模块时只须要依照接口定义实现性能,不须要改变业务逻辑。洋葱圈架构(整洁架构)洋葱圈架构又称为整洁架构,联合了分层架构、六边形架构和畛域驱动设计特点的架构设计办法。洋葱圈架构是对六边形架构的进一步扩大,依赖关系仍然是内部依赖外部。参考畛域驱动设计,将依赖档次划分为 3 - 4 层甚至更多。从外向外顺次为:畛域模型、业务逻辑、畛域服务、根底能力、内部模块等。洋葱圈架构具备六边形架构的长处,采纳依赖倒置的准则使外部业务模型不再间接依赖于内部根底能力。内部模块的变动和替换不影响外部业务逻辑。采纳畛域驱动设计的办法划分实体和模型,利于业务规定的形象和业务模型的建设,对将来业务迭代的反对较好。洋葱圈架构使业务实体、业务模型和业务实现处在里层,保障了业务模型和实现的稳固,防止受到内部模块变动的影响。例如,使三方 SDK 或者数据库系统属于最外层,应用依赖注入的办法将它们的实现传入外部逻辑。当替换三方 SDK 或者数据库系统时,依照接口定义实现具体细节即可。不须要对外部逻辑进行改变。畛域驱动设计办法畛域驱动设计简称为 DDD(Domain-Driven Design)。精确来说它不是一个架构设计办法,而是一种以业务剖析和划分来驱动零碎架构设计的软件开发办法。它强调辨认业务的外围问题域来确定问题边界,同时将问题域进行合成升高剖析的复杂度。DDD 强调通过关注业务外围晋升业务价值。上面是 DDD 的一些外围概念,咱们做一些简略的介绍。畛域:有确定的范畴和边界的业务问题域。实际上是咱们要解决什么业务问题的形象形容。比方提供给用户以后地位、目的地地位且提供达到信息是高德地图的问题域。子域:将大的问题域依据业务规定的不同拆分成的小问题域。比方高德地图的问题域太大了,难以解决。咱们能够将问题域拆分成定位、POI 搜寻、路线布局等子问题域。界线上下文:畛域之间的形象边界。封装了畛域内的概念、规定和模型。实体:具备惟一标识的、存在生命周期的对象。比方展现给用户可见的 POI 气泡是一个实体,它有状态和确定的生命周期。值对象:没有惟一标识和生命周期的对象,依附于实体而存在。比方 POI 信息是值对象,自身没有状态,只能依附于 POI 气泡这个实体而存在。聚合:畛域内一组实体、值对象的汇合。封装了汇合与外界的交互 

 应用 DDD 对业务问题进行剖析和拆解后,能够采纳任何一种架构设计办法,无论是分层架构、六边形架构或者整洁架构等。然而 DDD 要求架构设计从理论的业务场景登程,了解业务的外围问题。架构须要明确概念、规定的设计,并且保障业务模型的稳定性。应用分层架构展示 DDD 的畛域设计办法,将工程分为 4 层:基础设施层、畛域层、应用层和用户接口层。

 咱们所用的架构计划鹰巢我所在的团队——鹰巢业务组负责高德地图布局和导航的业务能力实现。它向下对接引擎层,包含定位引擎、导航引擎、渲染引擎等,向上对接前端 JS 层。除了承接性能宏大、逻辑简单的导航业务外,鹰巢还负责引擎能力的封装以及将这些封装能力向下层 JS 透出。在进行代码目录划分之前,鹰巢的性能实现也是依照模块化进行设计的,然而模块之间并没有明确的依赖关系。任何代码都能够相互的援用,这也就导致了工程中各模块之间有盘根错节的调用关系,很难以说分明某一个模块应该处于哪个地位,应该如何被援用。尽管咱们始终将工程代码分为框架层和业务层,然而框架层和业务层之间的依赖关系并不明确。业务层依赖框架层,框架层也依赖了业务层,并不合乎分层架构的设计准则,所以鹰巢的工程架构不属于分层架构。在咱们去年的代码目录划分的工作中,咱们最终参考畛域驱动设计的办法对代码目录进行了从新的组织和划分。将工程代码整体上分为 4 层:根底能力、业务层、工具层和接入层。以下是整体结构图:

 适配层与以下的 4 层不在同一个仓库,它蕴含了与前端 JS 交互的必要能力封装。依照模块的划分规定,咱们能够说,鹰巢的工程架构属于联合畛域驱动设计的松散型分层架构。它的特点是:依照畛域驱动设计对工程代码进行组织和划分,在业务层依照不同业务畛域划分代码模块采纳分层架构设计将工程分为多层,下层依赖于上层,上层不能依赖于下层下层任意模块都能够调用上层任意模块,属于松散型架构。更加灵便工程技术核心 C ++ 能力层(包含地图引擎层)在工程技术核心的语言能力框架中,从鹰巢、地图引擎到根底库都是 C ++ 语言实现。应用对立的流程管控它们的开发、构建、集成。在引擎架构降级之前的相当长的一段时间内,它们都属于松散型分层架构,以下是简化版的结构图:

 实际上,包含引擎库在内的 C ++ 层有几十上百个代码仓库,档次泛滥,且从下层到上层的依赖关系简单。如果将所有的依赖关系绘制进去,将是一个简单的网状。尽管整体架构仍然恪守了分层架构的设计准则:只能下层依赖上层。然而因为依赖档次和关系的简单,导致上层代码的改变对下层的影响很大,在构建时也经常出现库版本不匹配的抵触。这使得下层业务层常常处于不稳固状态,不利于下层业务的疾速迭代。并且上层能力降级也必然须要下层业务层做大工作量的适配。在去年的引擎架构降级中,抽离出形象层,使得各个仓库都依赖于形象接口,不再依赖于具体的实现。形象进去的形象层包含:InterfaceApp、InterfaceAR、InterfaceARWalk、InterfaceHorus、InterfaceMap、InterfaceVMap、InterfaceTBT、InterfacePosEngine 等。比方鹰巢和 TBT 都依赖于 InterfaceTBT 形象层,应用依赖倒置的准则在 App 初始化时将 TBT 的实例化对象设置给鹰巢。鹰巢通过调用实例化对象的形象接口拜访 TBT 的能力。同理,鹰巢和渲染都依赖于 InterfaceMap 形象层。这种形式使得下层的业务层比较稳定,只有保障形象层接口的稳定性,业务层基本上就不会受到上层改变的影响。而且,当下层进行能力降级时,只有依照形象接口定义实现对应能力即可,不须要业务层做适配。从这方面来讲,在引擎架构降级后,引擎具备整洁架构的特色。然而并不能齐全称为整洁架构,因为从更大的视角来看(将根底库和 Native 层包含进去),仍然是松散型的分层架构。所以,咱们能够称之为具备整洁架构特色的松散型分层架构。总结对于架构设计的学习和了解,我认为很难的一点是:即便懂得很多情理还是很难把事件做好。泛滥的设计准则都是在不同业务场景下提出的,有些准则之间自身就是矛盾的。无论是架构设计办法还是设计准则,它们不是清规戒律,更不可能放之四海而皆准。它们的价值在于通知咱们应该摒弃什么,应该恪守什么。咱们不必那些技术官僚的词汇,用更接地气的形容来说,设计准则也只是要求咱们做到简洁、标准和易于了解而已。架构设计并不高端,它自身所产生的价值并不显著,真正可能产生价值的在于咱们以后正在走的路:如何了解咱们的业务问题。参考资料和书籍:利用架构之道:拆散业务逻辑和技术细节:https://www.cnblogs.com/alisy… Onion Architecture:https://jeffreypalermo.com/20…《架构整洁之道》、《畛域驱动设计 -ThoughtWorks 洞见》、《代码精进之路 - 从码农到工匠》、《UNIX 编程艺术》附录工程:是指以某组构想的指标为根据,利用无关的科学知识和技术手段,通过有组织的一群人将某个(或某些)现有实体(天然的或人造的)转化为具备预期应用价值的人造产品过程工程化:是指以提高效率、降低成本、保证质量保障为目标从而促成多人单干,实现功能强大,健壮性好的我的项目的伎俩和措施 开发者评测局第五期——函数计算 Serverless 评测征集令 开发者评测局上新啦,第五期评测函数计算 Serverless 江湖征集令来袭,全新玩法,权利降级,价值千元高级版产品乘风者收费限量专享。群雄争霸夺好礼,Beats 耳机、机械键盘、千元天猫超市卡等好礼等你来拿,公布你的评测成为江湖新“一代宗师”。点击这里,查看详情。原文链接:http://click.aliyun.com/m/100… 本文为阿里云原创内容,未经容许不得转载。

正文完
 0