专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

使用DDD和SpringHATEOAS构建一个MRP的API实例和源码elca

  通过一个具体的例子告诉你我们如何在 Java 中实现一个只允许根据业务规则定义良好的状态转换的域模型,然后使用 Spring 在一个REST-API 中发布它。看看我们如何构建一个完全由该 API 驱动的简单 Web 应用程序。该实现使用来自领域驱动设计(DDD) 的概念,这是一种软件工件试图与业务模型一致的方法,例如通过使用领域语言的术语。
  CRUD 应用程序的问题
  create-read-update-delete 或简短的 CRUD 方法非常容易实现,它是有状态 Web 应用程序中将图形用户界面 (GUI) 与后端集成的主要方法。因此,后端的实体仅对需要持久化的领域逻辑的状态进行建模,而不是对适用于产生有效状态更改的业务操作和业务规则进行建模。
  在最好的情况下,服务操作会为这些操作命名并包含实现所需业务规则的逻辑。这些规则很容易从整个代码库中泄漏出来,并且也经常出现在图形用户界面中,现在通常实现为在浏览器中运行的单页 Web 应用程序。
  结果,我们有一个应用程序,它带有 Martin Fowler 所说的 贫血域模型 ,以及一个难以理解和维护的相互纠缠的泥球,因为职责没有明确分离。基于启发式,GUI 对实体可以做什么做出假设,并实现与后端分离的导航逻辑。
  HATEOAS 和 Richardson 成熟度模型
  查阅文献以寻找解决此问题的解决方案,您可能会遇到 HTTP 应用程序 API 的 Richardson 成熟度模型 。它从普通的旧 XML 开始,这意味着 XML 内容被发布到 Web 服务端点。 在 1级API中 引入了资源的概念,允许单独操作后端实体,从而将大型服务端点分解为多个资源。 2级API进一步使用特定的 HTTP 动词,如PUT、DELETE或PATCH来细化操作的含义。Martin Fowler 称它提供了一组标准的动词,以便我们以相同的方式处理类似的情况,消除不必要的变化。 在第 3级,通过向每个响应添加特定于上下文的超链接,将可发现性融入 API,让客户了解接下来可以使用给定资源执行哪些操作,或链接相关资源。在这个级别,与 API 交换的"超文本"充当应用程序状态的引擎,植根于后端并通过超链接向客户端(在我们的例子中为 Web 前端)显示。
  REST-ful API 和 HAL 标准如何帮助我们解决将业务逻辑封装在后端的初始问题?让我们看一个具体的例子。
  示例领域:制造资源计划
  假设我们正在构建一个制造资源计划系统( MRP ),产品经理可以在其中准备和提交生产订单。然后制造商可以接受订单,指明预计的交货日期,并在产品生产后完成订单。
  此外,以下业务规则适用: 产品经理在提交后无法更改生产订单。 当制造商接受生产订单时,他必须指明未来可以完成订单的日期。
  使用 Spring 和 Angular 设置项目
  正如 Josh Long 一直告诉我们的,每个项目都应该从 https://start.spring.io 开始。事实上,该页面非常方便,让我们可以轻松地引导一个包含所需技术的新应用程序。对于我们的案例,我们选择以下依赖项: Spring Data JDBC:基于 java 数据库连接 (JDBC) 的直接 OR 映射器,使我们免于 JPA 的开销,非常适合持久化  DDD  风格的聚合 H2 数据库:用 Java 编写的关系数据库,可以开箱即用地在内存中运行 Rest Repositories:一个 Spring 库,允许将我们的聚合发布为 REST 资源 Lombok:一个字节码生成器,它极大地减少了样板代码的数量,并为 Java 提供了一些现代语言,如 Kotlin 或 TypeScript Spring Boot DevTools:一个开发依赖,它会在代码库的每次更改时自动重新启动应用程序
  聚合:状态符合业务规则的地方
  域驱动应用程序的核心是域模型。它不受技术和集成方面的影响,并尽可能地遵循商业模式和术语。因此,应用程序的状态在所谓的实体中被捕获,其中相关的实体可以被分组以形成一个聚合体。每个聚合都定义了应用程序内部的一致性边界,这意味着只有明确定义的状态更改才会在聚合内以事务方式发生。
  为了保持专注,让我们从一个非常简单的生产订单模型开始,没有子实体,只有四个字段: id:区分不同生产订单的标识符。为简单起见,我们将其建模为 aLong并让数据库对其进行初始化。注释告诉 Spring Data JDBC这@Id是主键。 name:生产订单的名称。提交后,名称不得再更改。 expectedCompletionDate:制造商在接受生产订单时提供的日期,表明制造过程的计划完成 state:根据领域模型的生产订单状态。它可以假定值DRAFT, SUBMITTED, ACCEPTED, COMPLETED, 建模为枚举。
  我们使用 Lombok 的注解对类进行@Getter注解,它生成字节码以使用 getter 来检测我们的(普通)聚合,以便从外部访问这些字段的值。
  我们现在如何确保域模型只允许定义良好的状态转换,而不是通过 setter 展示所有字段?答案是:通过执行各自的业务操作。
  当然,我们可以使用构造函数来创建我们的实体。然而,我更喜欢提供一个静态工厂方法,它允许我们使用适当的业务术语作为名称,而不是相当技术性的new语句。creat- 方法将产品订单的名称作为单个参数并将状态初始化为DRAFT。当聚合被持久化到数据库时,该id字段稍后将由框架初始化。
  初始的create方法: package com.example.demo.productionorders;  import java.time.LocalDate;  import org.springframework.data.annotation.Id;  import lombok.Getter; import lombok.val;  @Getter public class ProductionOrder {      @Id     private Long id;     private String name;     private LocalDate expectedCompletionDate;     private ProductionOrderState state;      public static ProductionOrder create(String name) {         val result = new ProductionOrder();         result.name = name;         result.state = ProductionOrderState.DRAFT;         return result;     }      public enum ProductionOrderState { DRAFT, SUBMITTED, ACCEPTED; }  }
  Repository库和基本 REST API
  为了将我们的聚合持久化到数据库并从那里检索它,我们定义了一个接口,为简单起见扩展了CrudRepositorySpring Data 的接口。我们没有在其名称中使用"存储库",而是将其命名ProductionOrders为持久保存我们领域无处不在的语言。
  为了将我们的聚合持久化到数据库并从那里检索它,我们定义了一个接口,为简单起见扩展了CrudRepositorySpring Data 的接口。我们没有在其名称中使用"存储库",而是将其命名ProductionOrders为坚持我们领域无处不在的语言。package com.example.demo.productionorders;  import org.springframework.data.repository.CrudRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource;  @RepositoryRestResource public interface ProductionOrders extends CrudRepository {  }
  启动应用程序并通过 curl 命令行工具查询其 API,我们得到以下响应:$ curl http://localhost:8080/api  {      "_links" : {      "productionOrders" : {        "href" : "http://localhost:8080/api/productionOrders"      },      "profile" : {        "href" : "http://localhost:8080/api/profile"      }    }  } 
  你注意到字段_links了吗?是的,Spring Data REST默认产生HAL格式的响应。它向我们表明,API提供了一个集合资源 "productionOrders",包括一个如何导航的链接。如果每个资源都提供了客户需要的所有链接,以导航到相关的资源和调用动作,那么我们就可以得出以下结论。
  客户端只需要知道一个URL,那就是"/api"。在一个真正的REST-ful API中,所有其他的URL都可以从API的响应中检索出来。
  为了进一步说明这个概念,我们在DemoApplication类中创建并持久化一些生产订单,然后关注productionOrders资源的href-Property。$ curl http://localhost:8080/api/productionOrders {   "_embedded" : {     "productionOrders" : [ {       "name" : "Order 1",       "expectedCompletionDate" : null,       "state" : "DRAFT",       "_links" : {         "self" : {           "href" : "http://localhost:8080/api/productionOrders/1"         },         "productionOrder" : {           "href" : "http://localhost:8080/api/productionOrders/1"         }       }     }, {       "name" : "Order 2",       "expectedCompletionDate" : null,       "state" : "DRAFT",       "_links" : {         "self" : {           "href" : "http://localhost:8080/api/productionOrders/2"         },         "productionOrder" : {           "href" : "http://localhost:8080/api/productionOrders/2"         }       }     } ]   },   "_links" : {     "self" : {       "href" : "http://localhost:8080/api/productionOrders"     },     "profile" : {       "href" : "http://localhost:8080/api/profile/productionOrders"     }   } } 
  我们看到,两个生产订单被返回,包含在HAL规范定义的特殊字段_embedded的一个属性中。如果与一个资源的交互需要额外的信息,例如下拉列表的值,以过滤特定状态下的生产订单,这些数据可以被添加到响应的_embedded属性下的另一个字段。
  每个生产订单资源都提供一组链接,默认情况下是相当琐碎的:一个自我链接和一个生产订单链接,都指向资源本身。作为下一步,我们现在将把业务操作添加到生产订单类中,并把调用它们的端点作为附加链接公开。
  增加业务行为
  如果我们回顾一下业务需求的大纲,我们的聚合体需要支持以下操作。当处于DRAFT状态时允许重命名一个向制造商提交订单的操作一个接受订单的操作,提供预期交货日期
  该实现遵循我们在创建方法中已经使用的方法。我们没有提供getters和setters,而是用符合我们领域语言的名字来实现方法:renameTo、submit、accept。
  如上所述,聚合被看作是一致性的边界。由于额外的方法不再是静态的,我们可以直接利用类的字段来执行所需的业务规则,并允许只执行定义明确的状态转换。例如,我们可以完全确定永远不会遇到没有完成日期的已接受的生产订单,这是我们第二个业务规则的要求。与基于setter的方法相比,这是一个多么大的区别啊!
  在生产订单总量中实施三种业务操作,只允许有明确的状态转换。package com.example.demo.productionorders;  import java.time.LocalDate; import java.util.Objects;  import org.springframework.data.annotation.Id;  import lombok.Getter; import lombok.val;  @Getter public class ProductionOrder {      @Id     private Long id;     private String name;     private LocalDate expectedCompletionDate;     private ProductionOrderState state;      public static ProductionOrder create(String name) {         val result = new ProductionOrder();         result.name = name;         result.state = ProductionOrderState.DRAFT;         return result;     }          public ProductionOrder renameTo(String newName) {         if (state != ProductionOrderState.DRAFT) {             throw new IllegalStateException("Cannot rename production order in state " + state);         }         name = newName;         return this;     }              public ProductionOrder submit() {         if (state != ProductionOrderState.DRAFT) {             throw new IllegalStateException("Cannot submit production order in state " + state);         }         state = ProductionOrderState.SUBMITTED;         return this;     }      public ProductionOrder accept(LocalDate expectedCompletionDate) {         if (state != ProductionOrderState.SUBMITTED) {             throw new IllegalStateException("Cannot accept production order in state " + state);         }         Objects.requireNonNull(expectedCompletionDate, "expectedCompletionDate is required to submit a production order");         if (expectedCompletionDate.isBefore(LocalDate.now())) {             throw new IllegalArgumentException("Expected completion date must be in the future, but was " + expectedCompletionDate);         }         state = ProductionOrderState.ACCEPTED;         this.expectedCompletionDate = expectedCompletionDate;         return this;     }      public enum ProductionOrderState { DRAFT, SUBMITTED, ACCEPTED; }  }   
  在REST API中公开业务能力
  作为下一步,我们现在要在REST API中公开业务操作。我们需要这样做的成分是。为每个行动提供新的端点,其形式为/api/productionOrders/{id}/{action}。ProductionOrder资源的HAL表示中的链接
  仔细想想,如果我们只暴露一个指向端点的链接,如果相应的动作实际上是允许的,这不是很好吗,这取决于特定生产订单的状态?这可以通过以下方式轻松实现。
  我们首先实现一个ProductionOrderController类,并在类的层面上将其映射到/api/productionOrders端点(如果你在映射中遇到麻烦,请参见bug https://github.com/spring-projects/spring-data-rest/issues/1342)。
  这使得我们可以通过额外的方法来扩展Spring Data REST提供的标准API:rename, submit, accept。这些方法从路径中获取生产订单的ID,并从请求体中获取任何额外的必要参数。由于与网络技术的集成是应用层的问题,我们把它放在子包web中,以便将其与领域逻辑明确分开。
  在聚合上应用动作的模式总是相同的:从持久性存储中加载聚合,调用业务操作,并将其保存到存储中。这同样适用于我们案例中的关系型持久化,但也适用于事件源模型。为了简单起见,我们在控制器中做了所有的事情,而在一个更大的或更纯粹的应用中,控制器将委托给一个域服务。
  关于这篇博文的主题,更有趣的部分是通过实现Spring HATEOAS的RepresentationModelProcessor接口。在这个方法过程中,它把生产订单包装成一个实体模型。这个实体模型允许向生产订单资源添加额外的链接。因为该模型也提供了生产订单本身,我们可以很容易地检查它的状态,然后决定是否生成一个特定的链接。
  Spring提供了静态的辅助方法linkTo和methodOn来动态地导出引用的控制器方法的URL。package com.example.demo.productionorders.web;  import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;  import java.time.LocalDate;  import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.server.RepresentationModelProcessor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException;  import com.example.demo.productionorders.ProductionOrder; import com.example.demo.productionorders.ProductionOrder.ProductionOrderState; import com.example.demo.productionorders.ProductionOrders;  import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.Value; import lombok.val;  @RestController @RequestMapping("/api/productionOrders") @RequiredArgsConstructor public class ProductionOrderController implements RepresentationModelProcessor> {      public static final String REL_RENAME = "rename";     public static final String REL_SUBMIT = "submit";     public static final String REL_ACCEPT = "accept";          private final ProductionOrders productionOrders;          @PostMapping("/{id}/rename")     public ResponseEntity<?> rename(@PathVariable Long id, @RequestBody RenameRequest request) {         return productionOrders.findById(id)             .map(po -> productionOrders.save(po.renameTo(request.newName)))             .map(po -> ResponseEntity.ok().body(EntityModel.of(po)))             .orElse(ResponseEntity.notFound().build());     }      @PostMapping("/{id}/submit")     public ResponseEntity<?> submit(@PathVariable Long id) {         return productionOrders.findById(id)             .map(po -> productionOrders.save(po.submit()))             .map(po -> ResponseEntity.ok().body(EntityModel.of(po)))             .orElse(ResponseEntity.notFound().build());     }          @PostMapping("/{id}/accept")     public ResponseEntity<?> accept(@PathVariable Long id, @RequestBody CompleteRequest request) {         return productionOrders.findById(id)             .map(po -> productionOrders.save(po.accept(request.expectedCompletionDate)))             .map(po -> ResponseEntity.ok().body(EntityModel.of(po)))             .orElse(ResponseEntity.notFound().build());     }          @Override     public EntityModel process(EntityModel model) {                 val order = model.getContent();         if (order.getState() == ProductionOrderState.DRAFT) {             model.add(linkTo(methodOn(getClass()).rename(order.getId(), null)).withRel(REL_RENAME));             model.add(linkTo(methodOn(getClass()).submit(order.getId())).withRel(REL_SUBMIT));         }         if (order.getState() == ProductionOrderState.SUBMITTED) {             model.add(linkTo(methodOn(getClass()).accept(order.getId(), null)).withRel(REL_ACCEPT));         }                         return model;     }          @ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class})     void handleValidationException(Exception exception) {         throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, exception.getMessage());     }          @Value     static class RenameRequest {         @NonNull String newName;     }      @Value     static class CompleteRequest {         @NonNull LocalDate expectedCompletionDate;     }      } 
  再次查询productionOrders资源为我们提供了每个生产订单资源上的新链接。$ curl http://localhost:8080/api/productionOrders {   "_embedded" : {     "productionOrders" : [ {       "name" : "Order 1",       "expectedCompletionDate" : null,       "state" : "DRAFT",       "_links" : {         "self" : {           "href" : "http://localhost:8080/api/productionOrders/1"         },         "productionOrder" : {           "href" : "http://localhost:8080/api/productionOrders/1"         },         "rename" : {           "href" : "http://localhost:8080/api/productionOrder/1/rename"         },         "submit" : {           "href" : "http://localhost:8080/api/productionOrder/1/submit"         }       }     }, {       "name" : "Order 2",       "expectedCompletionDate" : null,       "state" : "DRAFT",       "_links" : {         "self" : {           "href" : "http://localhost:8080/api/productionOrders/2"         },         "productionOrder" : {           "href" : "http://localhost:8080/api/productionOrders/2"         },         "rename" : {           "href" : "http://localhost:8080/api/productionOrder/2/rename"         },         "submit" : {           "href" : "http://localhost:8080/api/productionOrder/2/submit"         }       }     } ]   },   "_links" : {     "self" : {       "href" : "http://localhost:8080/api/productionOrders"     },     "profile" : {       "href" : "http://localhost:8080/api/profile/productionOrders"     }   } } 
  你看到Spring并没有公开聚合的ID属性。我们稍后会看到,我们不需要在客户端知道它,因为它包含在链接中。
  还请注意,每个链接都有一个关系属性,简称 "rel"。这个属性非常重要,因为它定义了与API客户端的契约,即一个特定资源存在哪些链接。我们很快就会看到;我们的后端现在已经完成了,我们可以继续在前端利用它了。
  在前端消费HAL模型
  正如我之前所说,一个真正的REST-ful API的客户端应该只知道一个URL。/api。客户端调用的任何其他URL都应该从响应中的链接中获得。
  从我们Angular应用程序的顶级组件(即AppComponent)中发出对基本URL的请求,并将其作为输入传递给子组件,这将是一个自然的选择。为了保持简单,我们在ProductionOrderListComponent中做了所有事情,作为onInit方法的一部分获取API,并将响应存储在字段根中。见下面代码
  为了显示生产订单,我们在类顶部的@Component-decorator中添加一个HTML模板,并通过跟踪根资源中与 "productionOrders "有关的链接,从后台加载productionOrders。正如我们在上面看到的,这个链接的url属性是http://localhost:8080/api/productionOrders,但前端对此是不知道的。事实上,后端可以在一个完全不同的URL下提供生产订单,而我们的前端仍然可以工作。只有 "productionOrders "这个关系,也就是后端和前端之间的契约,必须保持稳定。
  最初的生产订单列表组件,首先加载API资源,然后通过各自关系下提供的URL加载生产订单。import { HttpClient } from "@angular/common/http"; import { Component, OnInit } from "@angular/core"; import { ProductionOrderResource } from "../model";  const API = "/api"; const REL = "productionOrders";   @Component({   selector: "app-production-order-list",   template: `   
  • "let order of productionOrders">{{order.name}} "order.expectedCompletionDate"> Expected for {{order.expectedCompletionDate|date}} ({{order.state}})
`, styleUrls: ["./production-order-list.component.css"] }) export class ProductionOrderListComponent implements OnInit { root: any; productionOrders?: ProductionOrderResource[]; constructor(private http: HttpClient) { } ngOnInit(): void { this.http.get(API).subscribe( response => { this.root = response; this.reload(); }, error => alert(error) ); } private reload(): void { if (this.root) { this.http.get(this.root._links[REL].href).subscribe( response => this.productionOrders = response._embedded[REL], error => alert(error) ) } } }   接下来,我们需要添加一种方式,让用户可以在模型上执行相应的业务操作。最简单的方法是为当前允许的每个动作在生产订单旁边添加一个按钮。   为每个生产订单添加按钮,根据生产订单资源中相关链接关系的存在与否,显示或隐藏这些按钮。
  • "let order of productionOrders">{{order.name}} "order.expectedCompletionDate"> Expected for {{order.expectedCompletionDate|date}} ({{order.state}})
  为了决定一个给定的动作是否被允许,以及相应的按钮是否应该被显示,我们查询底层资源,以获取与给定关系的链接。事实上,我们只是实现了一个功能切换:如果该关系被提供为一个链接,则该动作被启用,否则它在GUI中被隐藏。   请注意,示例代码(你可以在这篇文章的末尾找到链接)添加了一些接口,以允许对生产订单资源的_links-property进行类型安全的访问。   最后,我们只需要为那些需要提交额外数据的动作添加特殊处理。在这里,我们再次使用最简单的解决方案,使用本地的提示控制,在重命名动作的情况下接受新的名称,在接受动作的情况下接受预期完成日期。   一个非常简单的 "能 "和 "做 "方法的实现,利用关系和联系或生产秩序资源。can(action: string, order: any): boolean { return !!order._links[action]; } do(action: string, order: ProductionOrderResource): void { var body = {}; if (action === "rename") { const newName = prompt("Please enter the new name:"); if (!newName) { return; } body = { newName: newName }; } else if (action === "accept") { const expectedCompletionDate = prompt("Expected completion date (yyyy-MM-dd):"); if (!expectedCompletionDate) { return; } body = { expectedCompletionDate: expectedCompletionDate } } const url = order._links[action].href; this.http.post(url, body).subscribe( _ => this.reload(), response => alert([response.error.error, response.error.message].join(" "))); }   因为我们在can-method中检查了一个链接的存在,所以我们可以通过提取该链接的href-property轻松地确定要发布正文的URL。这样一来,我们的小演示程序的前端也就完成了。   总结   尽管许多开发者知道REST-ful APIs的Richardson成熟度模型,但只有少数人使用嵌入式链接来驱动应用状态。基于Open API(以前称为Swagger)的API文档将重点放在绝对URL上,而不是关系上。因此,前端代码经常被固定的URL所束缚,并复制了许多最好在后端进行的逻辑。   Spring HATEOAS为利用REST的全部潜力提供了所有必要的工具。因此,HAL是一个简单而强大的链接关系标准,可以很容易地被Angular应用程序消费,很适合选择性地发布遵循领域驱动设计原则的领域模型的业务操作。本文展示的方法可以帮助大大降低Web应用的复杂性,使业务逻辑远离前端,并使前端的行为基于后台的状态而 "恰到好处"。   你可以在这里找到源代码: https://github.com/sth77/spring-angular-ddd-hateoas 。   原文链接:https://www.jdon.com/58628

2020年东南亚Q2电商走势非必需品市场均下滑随着新冠疫情对生活的沉痛冲击,消费者们大幅度转用网购方式添购日常用品。根据上半年数据显示,东南亚电商在展现了前所未有的可观增长。为此,东南亚电商比价聚合平台iPriceGroup与小白主播从0开始挑战困难生存模式第二天黑夜我的世界里僵尸都学会抱团玩战术了,真是僵尸不可怕会兵法的僵尸才可怕。一招声东击西差点骗过玩家。时间过得真快,一转眼就到了第二天的夜晚了,这才刚刚夜晚僵尸就刷出来开始围殴主播了。主播下载数据出炉TikTok在全球到底有多火中美贸易战爆发后,中兴华为和TikTok接二连三遭到美国制裁。美国总统特朗普政府多次以涉嫌威胁美国国家安全为由,频频打压中企。前阵子国际版TikTok更是被美国行政压力下走向被迫出小白主播从0开始挑战困难生存模式第二天白天小白主播从0开始挑战困难生存模式第二天白天终于坚持到第二天的天亮了,但是由于是雨天并没有太阳,所以还有漏网的僵尸需要解决一下。就这么一个落单的僵尸没有什么可怕的,用我的镐子慢慢将他小白主播从0开始挑战困难生存模式第一天小白主播从0开始挑战困难生存模式第一天哇,现在已经进入我的世界了,运气不错啊,出生在了一个树木茂盛的地方。树木多动物就会多,这样就解决了木头跟食物的问题了。玩我的世界首先要记住的一穿上军装的明星更要对得起几分军装所带来的荣誉感韩红的身影出现在郑州获嘉县,带队支援河南抗洪救灾现场。韩红入伍曾在空政歌舞团任副团长。现在虽已退伍。在这次抗洪救灾的现场,遇到了交警同志后还是习惯的以军礼相敬。这种军人的习惯是刻在SteamDeck掌机和任天堂OLED哪个更受欢迎?东南亚数据有话说最近,Valve宣布了其第一款激动人心的掌上游戏机SteamDeck,紧接着任天堂公布了他们即将推出的掌上游戏机NintendoSwitchOLED。随着他们并排的公告,人们不禁对贵州抗洪英雄何国勇遇难。愿你一路走好,在天堂安好在河南遭受洪灾时,全国各地的抗洪救灾救援队员齐集而来。7月24号,早上七点。安越环境有限公司救援队到达河南新乡展开救援。7月26日零晨115分,其中一名来自贵州省黔西南州义龙新区龙鸿星尔克等民族企业需自强国人的支持应转化为动力河南郑州爆发特大洪灾。爱国的良心企业,在自身经营状况不良好的情况之下。都尽心尽力的捐款捐物,支援灾区。每个民族企业自身经营状况都有问题。但是支援灾区的爱心不会减少。民众知道以后。更央视又决定转播NBA。2月20日晚,国内篮球记者赵环宇爆料央视将在3月初的全明星周末恢复NBA的赛事转播。赵环宇的心情看起来很不错,他似乎想为此高歌一曲。随后,又有球迷询问央视是只播全明星一场比赛,还是为什么现在有那么多的人夸德艺双馨的艺术家?侯二。在头条上面发布了几张照片。配文,什么是你们的快乐?男人简单的快乐。这个不是重点。重点是下面网友对他的评论。而且都是点赞,排名前几的评论。真是看得出网友们已经非常用心的在评论。
陶克陶胡这个人,有人说是起义英雄,有人说是叛国反贼。你怎么看?我是萨沙,我来回答。电影无名里面,郭富城对周润发说过一句名言你说的再天花乱坠,黑的也不会变成白的。陶克陶胡无论因为什么原因起兵,他屠杀无辜满汉平民,叛国投靠沙俄,是铁一般的事实。蒙王者荣耀网友称名刀才是真正的肉装,魔女不详等是虚假的肉装!你觉得呢?自从狄仁杰跟曹操花木兰达摩杨戬这四个战士英雄的那一张图片出来之后(论真正的战士与虚假的战士),网友们就一直在自制各种王者荣耀真正的XX的图片,这不前两天就出来了真正的刺客真正的射手手游每日榜单(2021年9月2日)每日更新游戏榜单和新鲜资讯,关注我不迷路。关注我的都棒棒哒!今天是2021年9月2日,首先来看看今天的畅销榜单变化今天具体的榜单如下今日IOS畅销榜紫龙游戏天地劫幽城再临排名第10巫师3单机版免安装巫师3是由CDProjektRED开发的动作角色扮演巫师系列游戏,也是根据小说所改编的,拥有引人入胜的情节和强大的故事性,配合其精致的写实风格的3D画面,使人仿佛置身于游戏之中,体DNF回归玩家,为什么超界防具齐了,还是没有伤害?谢谢邀请!从题主给出的图片,可以看出,这应该是一个回归玩家新建的剑影角色,防具是超界轻甲,首饰江山如画,特殊装备万物生灭,武器为什么是传说?难道上13碎掉了?没有宠物,也就是说,基雯神,君克,猛比,谁才是国服第一瑞雯?我看君克直播是最多的,主要说说君克的瑞文吧。直播风格我挺喜欢的,时不时来一声猪叫,还有祖传感冒,从第一次看他直播到现在那句感冒中,少说点话就一直在屏幕上挂着,这又是一个医学上的难题为什么csgo取消了盾牌?原因很简单防暴盾这个东西,会破坏游戏平衡性,有这个CT(反恐精英)方会有非常大的优势。玩过CS1。6的玩家,应该都知道这个盾的恶心,在那个年代里,正规的服务器里,还有职业比赛中,是经典街机游戏三国志中的魏延如何才能将脚刀威力发挥到极致?街机游戏三国志中谁的绝招伤害威力最大呢?赵云的升龙剑关云长的强压张飞的长拳黄忠的背弓斩,以及魏延的脚刀。似乎每一个人物的下上手都能打出不俗的伤害。关羽黄忠和张飞的技能,想要发挥好威亚瑟最恶心打法边惩奔跑流,真正的七进七出,让对手毫无游戏体验,应该怎么玩?这个玩法的确可行,在寒夜抖音视频中也有过推荐,我在昨天晚上试了一把,很不错!相比大家之前被惩戒暗信给刷了一波屏吧?但并不是所有的玩家都能够轻松玩好那样的打法,这个时候亚瑟就站出来了刺激战场最佳武器搭配排行榜,98k狙击枪垫底,还有信仰存在吗?这里是游戏震惊部,关于FPS射击游戏考验的不仅是玩家的操作,还有灵活的作战意识,同时玩家还要对枪械要有足够的了解,刺激战场做为的生存策略游戏,除了在游戏中疯狂向决赛圈移动,不过为了新樱花大战日本好评率超过95玩家表示很期待樱花大战是很多玩家记忆中的经典,而最近世嘉公布的PS4游戏新樱花大战也是受到很受日本玩家的青睐。有很多日本玩家表示,这次的世嘉展会很有意思,同时日本Niconico动画站也让玩家们
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软件