推荐好文: 2.详细介绍23种设计模式
如何在代码中杀死太多?if else即if else通过公司代码审查,可以提高代码质量
微服务springcloud环境下基于Netty搭建websocket集群实现服务器信息推送----netty是yyds
目录
- 1、背景
-
- 1.1. 前言
- 1.2. MVC模式 VS DDD模式
-
- 1.2.1. MVC存在的问题:
- 1.2.2. 使用DDD的意义:
- 1.3. 总结:
- 2、DDD领域驱动模型
-
- 2.1. 概念
-
- 1. DDD:
- 2. 战略设计:
- 3. 战术设计:
- 4. 问题空间:
- 5. 解决空间:
- 6. 事件风暴:
- 7. 通用语言:
- 8. 上下文:限界:
- 9. 上下文映射:
- 10. 领域:
- 11. 领域模型:
- 12. 领域事件:
- 13. 实体:
- 14. 聚合:
- 15. 聚合根:
- 16. 贫血模型:
- 17. 充血模型:
- 18. 领域服务:
- 19. 应用服务:
- 20. 仓库:
- 21. 工厂:
- 22. 防腐层:
- 2.4. 落地方式
-
- 2.4.1. 战略设计:
- 2.4.2. 战术设计:
- 3、DDD架构
-
- 3.1. 传统四层架构:
- 3.2. 四层架构改良版
-
- (1)用户接口层:
- (2)应用层:
- (3)领域层:
- (4)基础设施层:
- 3.3. 整洁架构(洋葱架构)
- 3.4. CQRS架构(命令查询隔离架构)
- 3.5. 六边形架构(端口适配器架构)
- 3.6. 总结
- 4、 CQRS实战
-
- 4.1. 概念
- 4.2. 架构图
- 4.3. 代码布局
- 4.4. 数据模型转换
- 4.5. 项目目录结构
- 5、总结

1、背景
1.1. 前言
小首先问你一个问题,也是高级工程师面试中经常问的问题,如何设计一个好的软件系统,或者一个高质量的大型软件系统应该有什么特点? 欢迎在评论区留言或关注【】微信搜索【】,公众号回复【】即可获取【】一份!【】即可获取【】PDF一份!回复【】可进入无广告交流群! DDD也是设计高质量软件系统的解决方案。DDD之前,小编建议读者还是需要具备一定的设计模式的思想,不太了解设计模式的可以先参考小编的文章: 2.详细介绍23种设计模式
DDD至于这个想法,最早是Eric Evans(埃里克·2003年埃文斯《Domain-Driven Design –Tackling Complexity in the Heart of Software》书中提出的一个概念,翻译过来就是应对领域驱动设计-软件核心复杂性的方法,但微服务当时并不流行,所以一直没有火起来,DDD最近流行的主要原因是微服务的东风。
1.2. MVC模式 VS DDD模式
MVC大家应该都很熟悉三层开发模式,现在公司开发基本都是这样。
MVC开发流程:
- 用户需求转化为产品需求
- 需求评审会pm解释需求转化为研发需求
- 研发人员根据需要设计库表结构
- 编写dao层代码
- 编写service代码
- 编写controller代码
一步一步,是不是感觉很丝滑?例如,当产品提出需求时,我们通常会考虑如何设计几个表来存储数据dao层,service层,controller实现这一功能的层。但严格来说,mvc本质上是一种面向数据的设计,主要关注数据,从低到上。虽然在开发速度方面有一定的优势,但如果你只追求开发速度,数据模型编程可以在短期内满足需求,但盲目追求速度,如果你的系统业务变化迅速,从长远来看,随着时间的增长,系统堆积,MVC短板会越来越明显。
1.2.1. MVC存在的问题:
1.新需求的发展将越来越困难。 2.代码维护越来越困难,一类代码太多,对吧?这是一堆屎山。 3.技术创新越来越难,代码没有时间重构,拖得越来越差。 4.测试越来越难,不能单元测试,一个小需求回到测试,太累了。
1.2.2. 使用DDD的意义:
单体架构局部业务扩张可分为微服务,微服务架构局部业务扩张可分为什么? DDD从软件系统的长期价值来看,需要解决这些问题的存在DDD,虽然从设计到开发一开始需要成本,但随着时间的推移,N2000年后,代码仍然非常整洁,有利于扩展和维护,高度自主,高度内聚,边界和领域的划分非常清晰。当然,对于简单的系统DDD反而用复杂了,杀鸡焉用宰牛刀! :是数据驱动,自低向上的思想,关注数据。 :是领域驱动,自上而下,注重业务活动。
1.3. 总结:
DDD 其实分层结构中的元素和三层结构差不多,只是在 DDD 在分层架构中,这些元素被重新分类,重新分层,确定了层与层之间的交互规则和责任界限。
2、DDD领域驱动模型
2.1. 概念
1. DDD:
DDD(Domain Driven Design)领域驱动模型是一种处理高度复杂领域的设计理念,不是一种结构,而是一种结构设计方法,是一种设计模式。直率地说,它是为了很好地拆卸和包装复杂的软件应用系统的各个部分,以达到高内聚低耦合的效果。
,DDD以高内聚低耦合为目的,模块化软件系统。
2. 战略设计:
指名词、动词分析和提取领域模型。官方解释说,在某一领域,核心是上下文的设计,主要集中在上下文的划分、上下文的映射和一般语言的设计上。
,在某个系统中,核心围绕子系统的设计;主要关注子系统的划分、子系统的交互方式和子系统核心术语的定义。
3. 战术设计:
使用领域模型来指导设计和编码的实现。官方解释,核心关注实体建模、定义值对象、实体等,更倾向于开发细节。
,上下对应的就是某一个子系统,子系统里代码实现怎么设计,就是战术设计要解决的问题。核心关注某个子系统的代码实现,以面向对象的思维设计类的属性和方法,和设计类图没有什么区别,只是有一些规则而已。就是 指导我们划分类。
4. 问题空间:
问题空间属于需求分析阶段,重点是明确这个系统要解决什么问题,能够提供什么价值,也就是关注系统的What与Why。 问题空间将问题域提炼成更多可管理的子域,是真对于问题域而言的。问题空间 在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD 的领域就是这个边界内要解决的业务问题域。
领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围,每个子域又包含了核心子域,支撑子域,通用子域。
领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小服务需要解决的问题域,构建合适的领域模型。
,就是系统下面有多个子系统,就是分了一些类型,比如电商系统,订单就是核心领域,支付调用银行,支付宝什么的就是支撑子域,相当于我们俗称的下游,通用子域,就是一些鉴权,用户中心,每个系统都会用到,就设计成通用子域,关键就是讨论过程如何得出这些问题域,是战略设计要解决的。
5. 解决空间:
解决方案域属于系统设计阶段,针对识别出来的问题域,寻求合理的解决方案,也就是关注系统的How。在领域驱动设计中,核心领域(Core Domain)与子领域(Sub Domain)属于问题域的范畴,限界上下文(Bounded Context)则属于解决方案域的范畴。 ,得出这些问题域之后,就基于这些问题域来求解,属于解决空间。相当于,知道了y=2x,知道了x是多少,然后求y的值。解决空间就是指,领域之间的关系是什么样子,每个领域中通用的术语 ,具体在领域内怎么实现代码,进行领域建模就可以了。 从问题域到解决方案域,实际上就是从需求分析到设计的过程,也是我们逐步识别限界上下文的过程。
6. 事件风暴:
,就是将软件开发人员和领域专家聚集在一起,完成领域模型设计(领域分析和领域建模)。划分出微服务逻辑边界和物理边界,定义领域模型中的领域对象,指导微服务设计和开发。
,是根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;然后分析每个上下文内部,抽取每个子域的领域概念,识别出哪些是实体,哪些是值对象;
,就是对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
DDD需要进行领域分析和领域建模,除了事件风暴之外实现的方法有,领域故事讲述,四色建模法,用例法等。
事件风暴是建立领域模型的主要方法,但是在 DDD 领域建模和系统建设过程中,有很多的参与者,包括领域专家、产品经理、项目经理、架构师、开发经理和测试经理等。对同样的领域知识,不同的参与角色可能会有不同的理解,那大家交流起来就会有障碍,怎么办呢?因此,在 DDD 中就出现了“通用语言”和“限界上下文”这两个重要的概念。
7. 通用语言:
:领域专家+开发人员。领域专家擅长某个领域的知识,专注于交付的业务价值。而开发人员则注重于技术实现,总是想着类、接口、方法、设计模式、架构等。这也就导致了团队交流的困难性。因此找到双方的通用语言是解决该问题的有效途径。
。在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是。 通用语言包含术语和用例场景,并且能够直接反映在代码中。通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。
,使用团队中大家都懂的概念,解决交流障碍的问题,使领域专家和开发人员能够协同合作,从而能够确保业务需求的正确表达。
8. 限界上下文:
:限界上下文主要用来封装通用语言和领域对象。 限界上下文可以拆分为两个词,限界和上下文。
:适用的对象一般是抽象事物,指不同事物的分界,指定某些事物的范围。
:个人理解就是语境。语言都有它的语义环境,同样,通用语言也有它的上下文环境。为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD 在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。限界上下文就是用来定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。 比如说,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。那么,领域边界就是通过限界上下文来定义的。
。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
,是业务问题最小粒度的划分。在某个业务领域中会包含多个限界上下文,我们通过找出这些确定的限界上下文对系统进行解耦,要求每一个限界上下文其内部必须是紧密组织的、职责明确的、具有较高的内聚性。,上下文对应的就是某一个子系统,系统之间要划分好边界。
9. 上下文映射:
上下文之间交互方式就是上下文映射,相对于系统里面这就是RPC,http等交互方式。
10. 领域:
从广义上讲,领域具体指一种特定的范围或区域。在DDD中上下文的划分完的东西叫作领域,领域下面又划分了,核心领域,支撑子域,通用子域。 :在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
:它是业务成功的主要因素和公司的核心竞争力 :没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域 :有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,就是支撑域。
,系统下面有多个子系统,就是分了一些类型,比如电商系统,订单就是核心领域,支付调用银行,支付宝什么的就是支撑子域,相当于我们俗称的下游,通用子域,就是一些鉴权,用户中心,每个系统都会用到,就设计成通用子域,关键就是讨论过程如何得出这些域,是战略设计要解决的。
11. 领域模型:
领域模型是对领域内的概念类或现实世界中对象的可视化表示。它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。 是描述业务用例实现的对象模型。它是对业务角色和业务实体之间应该如何联系和协作以执行业务的一种抽象。 领域模型分为领域对象和领域服务两大类,领域对象用于存储状态,领域服务用于改变领域对象的状态。
特点:
- 领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分;
- 领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等;
- 领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;
- 领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造;
- 领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求;
- 要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通共同努力,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型;
- 为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型;
- 领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;
12. 领域事件:
聚合之间产生的业务协同使用领域事件的方式来完成,领域事件就是将上游聚合处理完成这个动作通过事件的方式进行抽象。
在DDD中有一个原则,一个业务用例对应一个事务,一个事务对应一个聚合根,也就是。但在实际应用中,一个业务用例往往需要修改多个聚合根,而不同的聚合根可能在不同的限界上下文中,引入领域事件即不破坏DDD的一个事务只修改一个聚合根的原则,也能实现限界上下文之间的解耦。对于领域事件发布,在领域服务发布,在不使用领域服务的情况下,则由应用层在调用资源库持久化聚合根之后再发布领域事件。
一个事件可能当前限界上下文内也需要消费,即可能有多个限界上下文需要消费,一个事件对应多个消费者。
一个完整的领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。 :构建一个事件,需要唯一标识,然后发布; :发布事件前需要存储,因为接收后的事建也会存储,可用于重试或对账等;就是每次执行一次具体的操作时,把行为记录下来,执行持久化。 :服务内的应用服务或者领域服务直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等,支持同步或者异步。 :先将事件存储,然后再处理。 当然了,实际开发中事件存储和事件处理不是必须的。
:发布订阅模式,分为跨上下文(kafka,RocketMq)和上下文内(spring事件,Guava Event Bus)的领域事件。
e.g. 用户注册后,发送短信和邮件,使用spring事件实现领域事件代码如下:
1.创建用户注册事件
/** * 用户注册事件 * @Author WDYin **/
public class UserRegisterEvent extends ApplicationEvent {
public UserRegisterEvent(Object source) {
super(source);
}
}
2.用户监听事件
/** * 用户监听事件 * @Author WDYin **/
@Component
public class UserListener {
@EventListener(UserRegisterEvent.class)
public void userRegister(UserRegisterEvent event) {
User user = (User) event.getSource();
System.out.println("用户注册。。。发送短信。。。" + user);
System.out.println("用户注册。。。发送邮件。。。" + user);
}
@EventListener(UserCancelEvent.class)
public void userCancelEvent(UserCancelEvent event) {
User user = (User) event.getSource();
System.out.println("用户注销。。。" + user);
}
}
3.发布用户注册事件
/** * 发布用户注册事件 * @Author : WDYin */
@RunWith(value = SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class MyClient {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test() {
User user = new User();
//发布事件
applicationContext.publishEvent(new UserRegisterEvent(user));
}
}
13. 实体:
官方解释,实体和值对象会形成聚合,每个聚合一般是在一个事务中操作,一般都有持久性操作。聚合中,跟实体的生命周期决定了聚合整体的生命周期。说白了,就是对象之间的关联,只是规定了关联对象规则(必须是由实体和值对象组成的),操作聚合时类似hibernate的One-Many对象的操作,一起操作,不能单独操作。
e.g. 权限管理系统——用户实体,代码如下:
@NoArgsConstructor @Getter public class User extends Aggregate<Long, User> { /** * 用户id-聚合根唯一标识 */ private UserId userId; /** * 用户名 */ private String userName; /** * 姓名 */ private String realName; /** * 手机号 */ private String phone; /** * 密码 */ private String password; /** *
锁定结束时间 */ private Date lockEndTime; /** * 登录失败次数 */ private Integer failNumber; /** * 用户角色 */ private List<Role> roles; /** * 部门 */ private Department department; /** * 领导 */ private User leader; /** * 下属 */ private List<User> subordinationList = new ArrayList<>(); /** * 用户状态 */ private UserStatus userStatus; /** * 用户地址 */ private Address address; public User(String userName, String phone, String password) { saveUserName(userName); savePhone(phone); savePassword(password); } /** * 保存用户名 * @param userName */ private void saveUserName(String userName) { if (StringUtils.isBlank(userName)){ Assert.throwException("用户名不能为空!"); } this.userName = userName; } /** * 保存电话 * @param phone */ private void savePhone(String phone) { if (StringUtils.isBlank(phone)){ Assert.throwException("电话不能为空!"); } this.phone = phone; } /** * 保存密码 * @param password */ private void savePassword(String password) { if (StringUtils.isBlank(password)){ Assert.throwException("密码不能为空!"); } this.password = password; } /** * 保存用户地址 * @param province * @param city * @param region */ public void saveAddress(String province,String city,String region){ this.address = new Address(province,city,region); } /** * 保存用户角色 * @param roleList */ public void saveRole(List<Role> roleList) { if (CollectionUtils.isEmpty(roles)){ Assert.throwException("角色不能为空!"); } this.roles = roleList; } /** * 保存领导 * @param leader */ public void saveLeader(User leader) { if (Objects.isNull(leader)){ Assert.throwException("leader不能为空!"); } this.leader = leader; } /** * 增加下属 * @param user */ public void increaseSubordination(User user) { if (null == user){ Assert.throwException("leader不能为空!"); } this.subordinationList.add(user); } }
###14. 值对象: 官方解释,描述了领域中的一件东西,将不同的相关属性组合成了一个概念整体,当度量和描述改变时,可以用另外一个值对象予以替换,属性判等,固定不变。 ,不关心唯一值,具有校验逻辑,等值判断逻辑,只关心值的类。只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。比如下单的地址。
当你决定一个领域概念是否是一个值对象时,需考虑它是否拥有以下特征:
- 度量或者描述了领域中的一件东西
- 可作为不变量
- 将不同的相关的属性组合成一个概念整体(Conceptual Whole)
- 当度量和描述改变时,可以用另一个值对象予以替换
- 可以和其他值对象进行相等性比较
- 不会对协作对象造成副作用
- 当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它相应的行为。需要将值对象看成不变对象,不要给它任何身份标识, 还应尽量避免像实体对象一样的复杂性。
。该集合有若干用于描述目的、具有整体概念和不可修改的属性。该集合存在的意义是在领域建模的过程中,值对象可保证属性归类的清晰和概念的完整性,避免属性零碎。
:
/** * 地址数据 * * @Author WDYin * @Date 2022/5/24 */
@Getter
public class Address extends ValueObject {
/** * 省 */
private String province;
/** * 市 */
private String city;
/** * 区 */
private String region;
public Address(String province, String city, String region) {
if (StringUtils.isBlank(province)){
Assert.throwException("province不能为空!");
}
if (StringUtils.isBlank(city)){
Assert.throwException("city不能为空!");
}
if (StringUtils.isBlank(region)){
Assert.throwException("region不能为空!");
}
this.province = province;
this.city = city;
this.region = region;
}
}
14. 聚合:
官方解释,实体和值对象会形成聚合,每个聚合一般是在一个事务中操作,一般都有持久性操作。聚合中,跟实体的生命周期决定了聚合整体的生命周期。说白了,就是对象之间的关联,只是规定了关联对象规则(必须是由实体和值对象组成的),操作聚合时类似hibernate的One-Many对象的操作,一起操作,不能单独操作。
- 我们把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。
- 聚合是领域对象的显式分组,旨在支持领域模型的行为和不变性,同时充当一致性和事务性边界。
- 聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。
- 比如有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;而有的业务逻辑需要聚合 C 和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。
,但不再强调部分与整体的独立性。聚合是将相关联的领域对象进行显示分组,来表达整体的概念(也可以是单一的领域对象)。比如将表示订单与订单项的领域对象进行组合,来表达领域中订单这个整体概念。
15. 聚合根:
聚合根(Aggreate Root, AR)就是软件模型中那些最重要的以名词形式存在的领域对象。聚合根是主要的业务逻辑载体,DDD中所有的战术实现都围绕着聚合根展开。70%的场景下,一个聚合内都只有一个实体,那就是聚合根。 :聚合的根实体,最具代表性的实体。比如订单和订单项聚合之后的聚合根就是订单。
:
- 它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
- 它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
- 聚合根之间的引用通过ID完成。在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。 简单概括一下:
通过事件风暴(我理解就是头脑风暴,不过我们一般都是先通过个人理解,然后再和相关核心同学进行沟通),得到实体和值对象; 将这些实体和值对象聚合为“投保聚合”和“客户聚合”,其中“投保单”和“客户”是两者的聚合根; 找出与聚合根“投保单”和“客户”关联的所有紧密依赖的实体和值对象; 在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。
16. 贫血模型:
贫血模型具有一堆属性和set get方法,存在的问题就是通过pojo这个对象上看不出业务有哪些逻辑,一个pojo可能被多个模块调用,只能去上层各种各样的service来调用,这样以后当梳理这个实体有什么业务,只能一层一层去搜service,也就是贫血失忆症,不够面向对象。
:
public class User { private Long id; private String userName;//用户名 private String password;//密码 private String gesture; //手势密码 private String phone; //手机号码 private String email; private int status; //账户状态 private Date lockEndTime; //锁定结束时间 private int failNumber; //登录失败次数 public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGesture() { return gesture; } public void setGesture(String gesture) { this.gesture = gesture; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public Date getLockEndTime() { return lockEndTime; } public void setLockEndTime(Date lockEndTime) { this.lockEndTime = lockEndTime; } public int getFailNumber 标签:
交流变送器参数