UDN-企业互联网技术人气社区

板块导航

浏览  : 27
回复  : 0

[业务中台] 微服务架构下领域建模避坑指南

[复制链接]
HRC13293373087的头像 楼主
发表于 2019-7-5 09:56:07 | 显示全部楼层 |阅读模式
本帖最后由 HRC13293373087 于 2019-7-5 10:01 编辑

微服务架构下.jpg


前言

  微服务自2014年3月由Martin Fowler首次提出以来,在Spring Cloud、Dubbo等各类微服务框架的帮助下,以燎原之势席卷了整个IT技术界,成为了最主流的分布式应用解决方案。伴随着Service Mesh及Kubernetes(K8S)的出现更是将微服务架构推至顶峰。
01-01.png

  微服务架构(Microservice Architecture)是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦,并提供更加灵活的服务支持。
  经常有人在讨论:架构是设计出来的?还是演化出来的?我个人基于多年的经验认为,架构既是设计出来的,同时也是演化出来的,对于互联网系统,基本上可以说是三分设计,七分演化,而且是在设计中演化,在演化中设计,一个不断迭代的过程。
  正在兴起一种架构方法学——演化式架构,微服务架构就是一种典型的演化式架构,它能够快速响应市场用户需求的变化,而单体架构就缺乏这种灵活性。
01-02.png

  单体架构,即传统开发模式。和Microservice相对应,这种模式一般也被称为Monolithic(单体式开发)。
  Web应用程序发展的早期,大部分web工程是将所有的功能模块(service side)打包到一起并放在一个web容器中运行,很多企业的Java应用程序打包为war包。其他语言(Ruby,Python或者C++)写的程序也有类似的问题。
  假设你正在构建一个在线商店系统:客户下订单、核对清单和信用卡额度,并将货物运输给客户。很快,你们团队一定能构造出如下图所示的系统。
01-03.png

  这种将所有功能都部署在一个web容器中运行的系统就叫做单体架构(也叫:巨石型应用)。
  单体架构有很多好处:IDE都是为开发单个应用设计的、容易测试——在本地就可以启动完整的系统、容易部署——直接打包为一个完整的包,拷贝到web容器的某个目录下即可运行。
  但是,上述的好处是有条件的:应用不那么复杂。对于大规模的复杂应用,单体架构应用会显得特别笨重:要修改一个地方就要将整个应用全部部署(PS:在不同的场景下优势也变成了劣势);编译时间过长;回归测试周期过长;开发效率**等。另外,单体架构不利于更新技术框架,除非你愿意将系统全部重写(代价太高你愿意老板也不愿意)。
  微服务:是一种架构设计模式。在微服务架构中,业务逻辑被拆分成一系列小而松散耦合的分布式组件,共同构成了较大的应用。每个组件都被称为微服务,而每个微服务都在整体架构中执行着单独的任务,或负责单独的功能。每个微服务可能会被一个或多个其他微服务调用,以执行较大应用需要完成的具体任务。
01-04.png

  微服务时代,企业到底该如何拆分自己的微服务?本文将基于项目实践提炼出微服务拆分的一些理论与实践,供大家参考使用。

微服务时代,微服务到底该如何拆分?

  微服务拆分理论
  说到微服务,服务拆分是绕不过去的话题,但是微服务不是说拆就能拆的,有很多的前提条件和原则,本文主要针对拆分粒度和拆分维度进行说明。
  拆分粒度
  拆分粒度不应该过分追求细粒度,要考虑适中不能过大或过小。按照单一职责原则和康威定律,在业务域、团队还有技术上平衡粒度。拆分后的代码应该是易控制,易维护的,业务职责也是明确单一的。
   单一职责原则(Single Responsibility Principle):一个对象应该只包含单一的职责,并且该职责被完整封装在一个类里。这个从目的上很好理解,就是为了达到高内聚、低耦合这个目的。
 “康威定律”
  ·第一定律
  Communication dictates design 组织沟通方式会通过系统设计表达出来
  ·第二定律
  There is never enough time to do something right, but there is always enough time to do it over.
  时间再多一件事情也不可能做的完美,但总有时间做完一件事情
  ·第三定律
  There is a homomorphism from the linear graph of a system to the linear graph of its design organization
  线型系统和线型组织架构间有潜在的异质同态特性
  ·第四定律
  The structures of large systems tend to disintegrate during development, qualitatively more so than with small systems
  大的系统组织总是比小系统更倾向于分解
  我们评价架构好坏的原则:可以用变化的成本来衡量。架构的目的就是管理复杂性、易变性和不确定性。这样就能在系统演化**中一部分系统的改变不会影响另一部分的系统。
  微服务拆分原则:围绕业务功能进行垂直和水平拆分。大小粒度是难点,也是团队争论的焦点。
  不好的实践
  ·以代码量作为衡量标准,例如500行以内。
  ·拆分的粒度越小越好,例如以单个资源的操作粒度为划分原则。
  建议的原则
  ·功能完整性、职责单一性。
  ·高内聚低耦合,服务粒度适中,团队可接受。
  ·以业务模型切入。
  ·迭代演进式拆分,非一蹴而就。
  ·API的版本兼容性优先考虑。
  代码量多少不能作为衡量微服务划分是否合理的原则,因为我们知道同样一个服务,功能本身的复杂性不同,代码量也不同。还有一点需要重点强调,在项目刚开始的时候,不要期望微服务的划分一蹴而就。
  微服务架构的演进,应该是一个循序渐进的过程。在一个公司、一个项目组,它也需要一个循序渐进的演进过程。一开始划不好,没有关系。当演进到一个阶段时,微服务的部署、测试和运维等成本都非常低的时候,这对于你的团队来说就是一个好的微服务。
  拆分维度
  AKF扩展立方的核心是三条轴,每条轴都代表一个关于扩展的原则。这个立方很好地表示出了从0扩展性(立方体的前端左下角)到接近无穷大扩展性(立方体的后端右上角)的过程。
02-01.png

  三个简单原则几乎可以帮你扩展任何东西。虽然扩展系统和平台的方法有很多种,但有了这三个原则,那么在你前进的道路上,就没有什么与扩展相关的**了。
  通过克隆进行扩展——通过克隆或复制数据和服务,可以轻松地扩展事务。
  通过拆分不同的东西进行扩展——用名词和动词标识数据和服务,从而进行划分。如果拆分正确,那么事务和数据集都能得到有效扩展。
  通过拆分相近的东西进行扩展——通常拆分的是数据集。把客户划分到专用的独立的数据片或泳道,可以对事务和数据进行扩展。
  应运而生的领域驱动模型设计
  2003年Eric Evans 发表Domain-Driven Design -Tackling Complexity in the Heart of Software (领域驱动设计:软件核心复杂性应对之道),简称DDD.
  Eric认为:“领域驱动设计只有应用在大型项目上才能产生最大收益。”
  复杂项目的定义:
  由大量相互作用的部分组成的系统,与整个系统比起来,这些组成部分相对简单,没有中央控制,组成部分之间也没有全局性的通讯,并且组成部分的相互作用导致了复杂行为。
  领域驱动设计的提出距今已经十多年,但真正火起来大约是在2013年微服务被提出之后。DDD关注于业务,旨在实现业务架构到系统架构的映射。DDD基于面向对象设计,通过面向对象方式抽象系统关系。
  领域驱动设计是一种设计方法, 试图解决的问题是软件的难以理解, 难以演化。领域驱动设计试图分离技术实现的复杂性, 围绕业务概念来构建领域模型的方式来控制业务的复杂性。
  DDD主要又分为战略架构设计和战术架构设计,接下来我们将分别从战略和战术架构层面详尽地讨论了如何实现DDD.
  战略架构设计
  DDD的战略设计主要包括领域/子域、统一语言、限界上下文和架构风格等概念。
  DDD的设计过程大概如下图所示:
03-01.png

  那么什么是统一语言(UBIQUITOUS LANGUAGE)?
  解决问题首先要从理解问题入手,很多事情的难点不在于解决问题,而在于认知问题。关于统一语言必要性,有一个经典的通天塔故事,人类想建一座通天塔,进度很快,上帝害怕了,于是上帝让建造者说不通的语言,这样通天塔就再也没有能建起来了。统一语言是一件事情能顺利开展的基础。
03-02.png

  由于语言上存在鸿沟,领域专家们只能模糊地描述他们想要的东西,开发人员虽然努力去理解一个自己不熟悉的领域但也只能形成模糊的认识,结果就是各说各的话,或者都是一知半解,最后到上线前才会发现漏了这个漏了那个。
  统一语言也并不是像UML,XML Schema或Java这样的语言,它是一种自然的但经过浓缩的领域语言,它是一种开发与用户共享的语言,用来描述问题和领域模型。统一语言不是把从用户那里听到的内容翻译为开发的语言,而是为了减少误解,让用户更容易理解的草图,从而可以真正的帮助纠正错误,帮助开发获取有关的领域新知识。
  那么统一语言到底长什么样子?对DDD的UL没有标准的定义,可以是图,也可以是文字,UML的各种图依然是常见的表达方式。
03-03.png

  领域模型术语成为了不同角色间共同认知的语言。那么究竟何为领域模型?
  如果说事务脚本是面向过程的,那么领域模型就是面向对象的。面向对象的一个很重要的点就是:“把事情交给最适合的类去做”,即:“你得在一个个领域类之间跳转,才能找出他们如何交互”,Martin Flower 说这是面向对象中最难的部分。
  1.        模型是对客观世界事物的一种抽象和简化。
  2.        它是从某个角度反映人对客观世界事物的一种认识。
  3.        它用于对事物的本质进行深入细致的研究。
  领域:从广义上讲,领域即是一个组织所做的事情以及其中所包含的一切,每个组织都有它自己的业务范围和做事方式,这个业务范围以及在其中所进行的活动便是领域。当你为某个组织开发软件时,你面对便是这个组织的领域。
  子域:在DDD中,一个领域根据业务的耦合性被分割为若干的子域。子域是对问题空间的分解。
03-04.png

  限界上下文:DDD另一核心概念就是限界上下文。
  一般一个限界上下文对应一个子域。另外一个限界上下文有可能包含的不只一个子域。
  领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。贫血模型是指使用的领域对象中只有setter和getter方法(POJO),所有的业务逻辑都不包含在领域对象中而是放在业务逻辑层。有人将这里说的贫血模型进一步划分成失血模型(领域对象完全没有业务逻辑)和贫血模型(领域对象有少量的业务逻辑)。**模型将大多数业务逻辑和持久化放在领域对象中,业务逻辑(业务门面)只是完成对业务逻辑的封装、事务和权限等的处理。
  战术架构设计
  DDD在战术层面的要素,包括实体(Entity),值对象(Value Objects),聚合(Aggregate)、领域服务(Domain Services)、领域事件(Domain Events)和一些设计模式如资源库(Repository)、工厂(Factory)、同时还包含一些架构模式如分层架构、六边形架构、事务脚本架构、事件驱动架构、CQRS+ES架构等(架构模式本文略)。
03-05.png

  实体:许多对象不是由它们的属性来定义,而是通过一系列的连续性(continuity)和标识(identity)来从根本上定义的。只要一个对象在生命周期中能够保持连续性,并且独立于它的属性(即使这些属性对系统用户非常重要),那它就是一个实体。
  实体的定义隐藏了几个信息:
  ·两个实体对象,只要它们的标识属性值相等,哪怕标识属性以外的所有属性值都不相等,这两个对象也认为是同一个实体,这意味着两个对象是同一实体在其生命周期内的不同阶段。
  ·为了能正确区分实体,标识必须唯一。
  ·实体的标识属性值是不可变的,标识属性以外的属性值是可变的。如果标识值不大稳定,偶尔会变化,那么就无法将该实体在生命周期内的所有变化关联在一起,这可能导致严重的问题。
  值对象:当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它相应的行为。我们需要将值对象看成不变对象,不要给它任何身份标识,还应该尽量避免像实体对象一样的复杂性。
  为什么需要使用值对象,书中给了一个解释:使用不变的值对象使得我们做更少的职责假设。
  关于值对象,拥有以下基本特征:
  1.        它度量或者描述了领域中的一件东西。
  2.        它可以作为不变量。
   3.        它将不同的相关的属性组合成一个概念整体(Conceptual Whole)
  4.        当度量和描述改变时,可以用另一个值对象予以替换。
  5.        它可以和其他值对象进行相等性比较。
  6.        它不会对协作对象造成副作用。
  聚合(Aggregate)是实体(Entity)和值对象(Value object)的聚合,它定义了一个边界,在此边界中,数据必须保证数据完整性。
  聚合(Aggregate)有一个根Root entity,从这个Root entity可以获取Aggregate内部的其他Entity和Value object.
03-06.png

  ·分离业务逻辑和技术逻辑(资源库,分层架构),
  ·组织代码(值对象,实体,聚合,模块,服务),
  ·考虑竞争和一致性之间的权衡(聚合,域事件),
  ·明确阐述竞争与业务逻辑(域事件)之间的关系,
  ·强制解释代码中的业务概念(值对象,实体,域事件)。
  领域服务:领域中的一些概念不太适合建模为对象,即不适合归类到实体对象或值对象,因为它们本质上就是一些操作,一些动作,而不是事物。这些操作或动作往往会涉及到多个领域对象,并且需要协调这些领域对象共同完成这个操作或动作。
  如:库存可用量计算、MRP计算
  领域事件:
  A domain event is a full-fledged part of the domain model, a representation of something that happened in the domain. Ignore irrelevant domain activity while making explicit the events that the domain experts want to track or be notified of, or which are associated with state change in the other model objects.
  领域事件是一个领域模型中极其重要的部分,用来表示领域中发生的事件。忽略不相关的领域活动,同时明确领域专家要跟踪或希望被通知的事情,或与其他模型对象中的状态更改相关联。
  事件是构成物理世界的重要元素。领域驱动模型设计实践过程会针对事件进行事件风暴,通过事件风暴确定服务边界(见下文:事件风暴)。
  工厂和资源库:
  我们先来看一下领域对象的生命周期:
03-07.png

  整个领域对象生命周期具有如下特点及问题:
  1.在整个生命周期中维护完整性。
  2.防止模型陷入管理生命周期复杂性造成的**当中。
  通过三种模式来解决这些问题:
  1.聚合-定义对象的关系和边界,保证完整性。
  2.工厂-生命周期的开始阶段,创建和重建对象和聚合。
  3.资源库-生命周期的中间和末尾,提供查找和检索持久化对象及封装基础实施。
  聚合上面已经提及,接下来看一下工厂和资源库。
  在大型系统中,实体和聚合通常是很复杂的,这就导致了很难去通过构造器来创建对象。工厂就解决了这个问题,它把创建对象的细节封装起来,巧妙的实现了依赖反转。当然对聚合也适用(当建立了聚合根时,其他对象可以自动创建)。
03-08.png

  领域对象的生命周期管理中的模式:资源库
  Repository:协调领域和数据映射层,利用类似与集合的接口来访问领域对象。
03-09.png

  资源库封装了获取对象的逻辑,领域对象无须和底层数据库交互,它只需要从资源库中获取对象即可。资源库可以存储对象的引用,当一个对象被创建后,它可能会被存储到资源库中,那么下次就可以从资源库取。如果用户请求的数据没在资源库中,则会从数据库里取,这就减少了底层交互的**。
  资源库可以包含一个策略。它可能基于特定的策略来访问某个或者另一个持久化存储介质。它可能会对不同类型的对象使用不同的存储位置。总体的结果是领域模型本身与需要保存对象或它们的引用、访问底层持久化基础设施实现了解耦。
  领域驱动模型战术设计概念较多,分类总结如下:
  1.        领域驱动设计使用分层架构来隔离不同功能模块之间的耦合性;
  2.        使用实体,值对象和服务来表达领域模型;
  3.        聚合对实体和值对象进行封装,对一个聚合内的所有对象统一维护其生命周期,实体可以作为聚合根存在;
  4.        工厂对实体,值对象以及聚合的创建进行封装
  5.        资源库对聚合,实体的存取方法进行封装;
  6.        工厂和资源库都是基于聚合根的。
  基于微服务拆分理论及领域驱动模型设计,接下来我们将进行领域驱动模型设计实践。
  领域驱动模型设计实践
  ·分析业务模型——弱耦合在一起,高内聚
  ·确定服务边界
  分析业务模型(业务场景梳理)
  微服务拆分第一步是业务场景的梳理。业务场景即用户在使用一个产品时,主要的操作步骤和环节(暂不考虑具体的功能流程)。如何梳理业务流程,一般分为三步,
  1)业务调研:明确业务需求,了解在现实过程中业务实际上是如何运作的;
  2)梳理和展示:将调研的结果先梳理出主要业务的流程,然后对分支流程逐步拆解,直到无法拆解;
  3)查漏补缺:联合各职能部门评审需求,头脑风暴,一起梳理流程,一起完善流程。
  下图为领域驱动模型设计分析案例的业务场景梳理示例,供大家参考:
04-01.png

04-02.png

  确定服务边界(事件风暴)
  Event Storming是一种领域建模的实践。最初由Alberto Brandolini开发,经过DDD社区和TW的很多团队实践验证后,于2015年11月进入ThoughtWorks技术雷达。Event Storming被验证是一种快速探索负杂业务领域的方法。
  事件风暴是一个协作活动,将领域专家,技术架构师和设计师聚集在一起,发现系统或上下文的无处不在的语言。目的是试图捕获系统发生的事情 ,也就是事件。事件风暴将动态业务流程分析替代静态结构分析。
04-03.png

  什么是领域事件?
  “任何的业务事件都会以某种数据形式留下足迹。我们对于事件的追溯可以通过对数据的追溯来完成。你无法回到从前去看看到底发生了什么,但是却可以在单据的基础上,一定程度的还原当时事情发生的场景。当我们把这些数据的足迹按照时间的顺序排列起来,我们几乎可以清晰的推测出这个在过往的一段时间内到底发生了什么事情。”
04-04.png

  领域事件要点:
  ·业务关心的事件
  ·用“已发生”时态描述
  ·有时间顺序
  ·具体事件风暴步骤如下图所示:
04-05.png

  通过事件追寻因果关系(命令风暴)
  领域事件(Domain Events)是领域驱动设计(Domain Driven Design,DDD)中的一个概念,用于捕获我们所建模的领域中所发生过的事情。领域事件本身也作为通用语言(Ubiquitous Language)的一部分成为包括领域专家在内的所有项目成员的交流用语。比如,在用户注册过程中,我们可能会说“当用户注册成功之后,发送一封欢迎邮件给客户。”,此时的“用户已经注册”便是一个领域事件。
  领域事件主要用于解耦微服务,此时各个微服务之间将形成最终一致性。事件风暴活动有助于我们对微服务进行拆分,并且有助于我们深入了解某个领域。领域事件作为已经发生过的历史数据,在建模时应该将其创建为不可变的特殊值对象。存在多种方式用于发布领域事件,其中“在聚合中临时保存领域事件”的方式是值得推崇的。另外,我们需要考虑到聚合更新和事件发布之间的原子性,可以考虑使用XA事务或者采用单独的事件表。为了避免事件重复带来的问题,最好的方式是将事件的消费方创建为幂等的。
  那么什么是命令,又该如何进行命令风暴呢?
  对可能产生事件的命令进行建模,命令可以是:
  ·用户从UI界面进行的操作
  ·外部系统触发
  ·定时任务
04-06.png

  具体命令风暴步骤如下图所示:
04-07.png

  对命令和事件进行划分找到聚合边界(识别聚合)
  在领域驱动设计中,聚合是一组相关领域对象,其目的是要确保业务规则在领域对象的各个生命周期都得以执行。
  ·聚合边界内保证业务不变性(invariant)
  ·只能通过聚合根修改边界内对象
  ·聚合根有全局标识
04-08.png

具体寻找聚合步骤如下图所示:
04-09.png

  通过“限界上下文”划分微服务(寻找限界上下文)
  限界上下?明确地定义模型所应?的上下?。根据团队的组织、软件系统的各个部分的用法以及物?表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而?要受到边界之外问题的干扰和混淆。子域对应的是问题空间,限界上下文对应的是解决?方案空间。
  一般建议微服务的边界与限界上下文的边界进行对齐。
  限界上下?主要用来封装通?语?言和领域模型,显式地定义了领域模型的边界(所应用的上下?)。
  怎么理解上下文?
  例如物料模型在在采购领域是原料,在生产领域是产品,在库存领域是存货,在销售领域是商品。
  限界上下文是业务上的一种边界。上下文决定了工作边界,同时也决定了应用边界。如何识别限界上下文呢?
  “By Experience” —— Vanghn Vernon
  关注点分离
  1.        从聚合出发,一个聚合可能是最小颗粒度的限界上下文。
  2.        考虑聚合之间的业务相关性,合并高业务相关性的聚合。
  3.        考虑限界上下文的概念完整性,合并业务价值不明确的限界上下文。
  【Bad Smell】如果在不同的限界上下文中看到了完全相同的实体,往往意味着模型是错误的。
04-10.png

  当歧义真的到来的时候,我们需要一种工具来让开发团队明白,应用程序中正存在着两个不同的上下文。歧义是统一语言的头号大敌,我们必须铲除它,消除歧义的最好办法就是在上下文图中,将领域结构分拆在多个限界上下文中。
04-11.png

  还有一种更加另人困惑的情况,就是底层的概念相同,但是使用方式不同,最终导致了不同的模型。以电商系统的“商品”为例。库存商品和仓储商品可能物理上对应同一商品,共享了SKU数据。但仓储上下文不能调整库存上下文的销售价格,同样库存上下文也不应当关心仓储商品的物理存放位置。
04-12.png

  很多这种电商系统都需要与外部金融系统进行数据交换。即使设计两个模型之初是让它们展示相同的数据(至少在一定程度上),但随着时间的推移,它们还是会受到不同程度的影响,而且它们也会用于不同的目的。因此分离上下文边界上必须的。
04-13.png

  上下文图迫使我们将非软件的因素也包含在整体考虑中,这样我们更能识别出一些污点热区,而这些热区在传统架构分析的观点中是“不在范围内”的。比如,组织内的信息流通方式会在很大程度上影响最终的软件。通常,在小型组织中,组件自身的用途是定义上下文边界的主要因素,而在大型组织中,这个关键因素变成了沟通效率和项目组织方式。
04-14.png

  针对本文实践案例限界上下文划分如下图所示:
04-15.png

  我们进行微服务拆分一般也是从限界上下文最终到微服务的过程。其中主要从三个层面考虑,分别是领域逻辑层面、团队合作层面、技术实现层面。
04-16.png

  领域层(Domain层)是整个系统的核心层,该层维护一个使用面向对象技术实现的领域模型,几乎全部的业务逻辑会在该层实现。Domain层包含Entity(实体)、ValueObject(值对象)、Domain Event(领域事件)和Repository(资源库)等多种重要的领域组件。
  应用层是对领域层的组装和协调,同时还有任务管理功能,将来自用例流的请求转换为领域逻辑的执行流。面向用例设计,跟场景相关,是控制事务、安全的适宜场所。
04-17.png
  完整视图如下:
04-18.png
  下图为针对前文业务场景示例进行微服务开发实现示例供大家参考:
04-19.png

  示例基于用友云PaaS基础平台具体实现如下图所示:
04-20.png
04-21.png
04-22.png
04-23.png


总结和未来展望

  微服务 (Microservices) 成为了追求高市场响应力产品团队的必修课,如何有效划分微服务成为了服务化设计的第一步。很多团队在划分过程中缺少系统化的设计方法,为后续的实施埋下了巨大的隐患,最终会造成整体架构的失败,持续开发成本甚至高于单体架构。
  领域驱动设计 (DDD) 的战略建模为微服务划分提供了良好的指导,在我们近几年的实战中逐渐完善了领域设计的实践方法,能够有效结合事件风暴 (Event storming) 的形式从业务需求出发产生合理的服务划分,并帮助团队建立持续的演进机制,明确演进方向。
  通过领域驱动设计 (DDD) 来识别聚合关系和划分领域限界上下文,最终落地微服务架构。
05-01.png



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部