在对数据库进行单元测试之后,我们就可以开始微服务接口的开发了。完成数据库的开发之后,接口的开发就很简单了。下面的代码展示了订单数据查询和订单生成的设计实例:
@RestController
CRequestMapping("/order")@slf4j
publicclass OrderRestController {
@Autowired
private orderService orderService;
@GetMapping (value="/{id] ")
public Mono<0rder> fnidById(@Pathvariable string id){
return orderService.findById(id);
)
GetMapping()
public Flux<Order> findAll(Integer index,Integer size,Long userid, Longmerchantid,
Integer status, String start, String end){
try{
OrderQo orderQ0 = new OrderQo();
if(CommonUtils.isNotNul1(index)){
orderQo.setPage (index);
if(CommonUtils.isNotNull(size)){
orderQo .setsize(size);
if(CommonUtils.isNotNull (userid)){
orderQo.setUserid(userid);
}
if(CommonUtils.isNotNull (merchantid)){
orderQo.setMerchantid (merchantid);
}
if(CommonUtils.isNotNull(status)){
orderQo.setStatus(status);
if(CommonUtils.isNotNull (start)){
SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd
HH:mm: ss");
orderQo .setstart(sdf.parse(start));
}
if(CommonUtils.isNotNull (end)){
SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd
HH:mm:sS");
orderQo .setEnd(sdf.parse(end));
}
return orderService.findAll (orderQo);}catch (Exception e){
e.printStackTrace();)
return null;
CPostMapping()
public Mono<Void> save (CRequestBody Ordergo orderQo) throws Exception{
Order order = CopyUtil.copy(orderQo,Order.class);
List<orderDetail>detailList=
CopyUtil.copyList(orderQo.get0rderDetails(),OrderDetail.class);
order.setOrderDetails(detailList);
Mono<Void> response = orderService.save (order).then();
log.info("新增="+ response);
return response;
}
}
由于使用反应式的编程方法,所以不管是哪一种接口的设计,最后都必须返回一个异步序列,例如 Mono或者Flux。
对于分页查询,则必须检测每个查询参数,只有非空,才提供相关的查询条件。注意日期类型的参数,因为在传输过程中只能使用文本方式,所以最后在提交查询条件时必须将其转换为日期类型的数据。
在订单生成的设计中,因为接收的参数是查询对象OrderQo,所以最终必须将其转换成文档Order。每一个订单明细都必须进行转换。
集中式的数据管理可以在一个事务中完成,所以能保证数据的高一致性。微服务的多服务架构,使得数据可以由不同的微服务进行分散管理,所以想要保证数据的一致性,就必须有合理的设计。
对于订单来说,订单的状态变化与库存、物流、评价等各个服务息息相关,所以订单的状态变化,会涉及分布式事务管理的问题。比如当买家撤销订单时,库存服务的商品存量必须改变。
对于分布式事务管理,我们可以依据CAP原理的BASE理论实现数据最终一致性设计。
CAP (Consistency,Availability,Partition Tolerance)即一致性、可用性和分区容错性,三者不可兼得。
BASE(Basically Available,Soft State,Eventually Consistent)即基本可用、软状态和最终一致性。BASE是对CAP中一致性和可用性进行权衡的结果。
在微服务设计中,数据最终一致性设计主要使用两种方法实现,一种是通过接口调用实现实时同步操作,另一种是使用消息通道以事件响应的方式进行异步处理。
这里,我们以订单取消为例,使用异步消息传输,实现分布式事务管理的数据最终一致性设计。
订单取消的消息生成
首先,在order-restapi模块的项目对象模型配置中引入AMQP的消息组件依赖,代码如下所示:
<!--消息总线--><dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amap</artifactId></dependency>
其次,在配置文件中,设置连接RabbitMQ服务器的配置,代码如下所示:
spring:
rabbitmq:
addresses: amap://localhost:5672username: develop
password: develop
服务地址、端口、用户名和密码等参数需根据RabbitMQ的安装和配置进行设定。
下面,我们创建一个消息发送器MessageSender,用来发送 MQ (Message Queue),代码如下所示:
@service
public class MessageSender {
private static Logger logger =
LoggerFactory.getLogger (MessageSender.class);
@Autowired
private AmqpTemplate amqpTemplate;
public void OrderUpdateMsg (0rderQo orderQo){
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);messageProperties.setContentType("UTF-8");
String json = new Gson().toJson(orderQo);
Message message = new Message (json.getBytes(),messageProperties);
amapTemplate.send ("ordermsg.update", message);//接收端必须使用相同队列
logger.info("发送订单变更消息:{}", json) ;
}
}
这里使用查询对象 OrderQo发送了一个完整的订单信息。
注意,这里的消息队列的名称设定为“ordermsg.update”,所以,消息接收方必须使用这个队列名称才能接收订单状态变化的相关信息。
最后,我们在订单修改这个接口中,使用消息发送器生成一条异步消息,代码如下所示:
@RestController
@RequestMapping ("/order")@Slf4j
public class OrderRestController {
@Autowired
private OrderService orderService;
@Autowired
private MessageSender messageSender;
CPutMapping()
public Mono<Void> update (@RequestBody OrderQo orderQo) throws Exception{
Order order = CopyUtil.copy(orderQo, Order.class);
order.setModify(new Date());
List<0rderDetail> detailList =
CopyUtil.copyList(orderQo.get0rderDetails(),OrderDetail.class);
order.setorderDetails(detailList);
Mono<Void> response = orderService.save(order).then();
//发送 MQ消息,通知订单修改
messageSender.OrderUpdateMsg(orderQo);
log.info("修改="+ response);
return response;
}
}
上面就是在分布式事务设计中消息生产方的一个设计实例。接下来,我们看看消息消费者是如何接收消息的,以完成一次分布式事务的过程。
订单取消的库存变化处理
接收订单取消的消息处理是在库存管理项目goods-microservice的goods-restapi模块中实现的。其中,MQ的组件依赖和服务器连接的配置与前面的基本相同。
在库存接口微服务应用中,我们只需设计一个订单消息接收器MessageOrderReceiver,就可以完成消息监听、消息接收、消息处理的相关操作,代码如下所示:
@component
public class MessageOrderReceiver {
private static Logger logger=
LoggerFactory.getLogger (MessageOrderReceiver.class);
@Autowired
private GoodsService goodsService;
@RabbitListener(queuesToDeclare = CQueue (value = "ordermsg.update"))public void orderUpdate (byte[] body) {
logger.info("----------订单变更消息处理----------");try {
OrderQo orderQo =new Gson ().fromJson (new String (body,"UTF-8"),
OrderQo.class);
if(orderQo != null) {
logger.info("接收订单更新消息,订单编号="+orderQo.getorderNo());
if (orderQo.getStatus() !=null && orderQo.getStatus() <0){
List<0rderDetailQ0> list = orderQo.getorderDetails();
for (orderDetailQo orderDetailQ0 : list) {
Goods goods =
goodsService.getById(orderDetailQo.getGoodsid());
if(goods != null){
Integer num= goods.getBuynum() != null &&
goods.getBuynum() >0?
goods.getBuynum() - 1 :0;
goods.setBuynum (num) ;
goodsservice.update(goods);
logger.info("更新了商品购买数量,商品名称="+
goods.getName ());
}
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
这里我们监听了消息队列“ordermsg.update”,将接收的消息转换成查询对象OrderQo,这样,即可根据订单状态和订单明细中的商品数据,决定是否执行商品库存存量减少的操作,完成一次分布式事务管理的整个流程。
订单管理后台微服务是为商家提供的一个PC端的Web微服务应用,它的设计在订单微服务项目的order-web模块中。在这个模块中,含有微服务接口调用和页面设计等内容,与前面章节中类目管理和库存管理项目的设计大同小异。这里只针对一些不同点进行详细介绍,其他方面可以参照前面章节。
订单查询主页设计
订单后台主页控制器 OrderController的设计代码如下所示:
@RestController
@RequestMapping(" /order")
public class OrderController {
private static Logger logger =
LoggerFactory.getLogger(0rderController.class);
@Autowired
private OrderRestService orderRestservice;
CRequestMapping(value="/index")
public ModelAndView index (ModelMap model) throws Exceptionl
//订单状态枚举集合
StatusEnum[] statuses = StatusEnum.values();model .addAttribute( "statuses", statuses);
return new ModelAndview( "order/index");
@RequestMapping (value="/{id}")
public ModelAndview findById (@PathVariable String id,ModelMap model)
String json = orderRestService.findById(id);
OrderQo orderQo =new Gson().fromJson(json,OrderQo.class);
model .addAttribute ("status",
StatusEnum.valueOf(orderQo. getStatus()).getName ());
model.addAttribute( " order",orderQo);
return new ModelAndView("order/show");
}
@RequestMapping(value = "/list")
public Page<Map<String,0bject>> findAll(Ordergo orderQo) throws Exception {
String json = orderRestService.findPage (orderQo);
Pageable pageable = PageRequest.of (orderQo.getPage(),orderQo.getSize (),
null);
List<0rderQo> list = new Gson() .fromJson(json,new
TypeToken<List<0rderQ0>>(){}.getType());
for(0rderQo order : list){
order.setStatusStr (StatusEnum. valueOf (order.getStatus()) .getName ());
}
String count =orderRestService.getCount();
return new PageImpl(list, pageable, new Long (count));
}
}
这几个方法中都用到了订单状态的枚举类型集合StatusEnum,使用这个集合,为我们在订单查询和参数转换中提供很多方便。
其中在分页查询中,调用了两次订单接口,一次用来取得订单列表,另一次用来取得订单总数。通过这个总数,才能计算出总的页数。另外,对于列表中订单状态的显示,在这里提前进行了转换处理,这样在后面的页面设计中,就可以直接使用。
基于订单状态的枚举集合,主页页面设计中的查询条件设计代码如下所示:<li>
<label class="preInpTxt f-left">订单状态</label><select id="status" name="status">
<option value="">全部</option>
<option th:each="status:${statuses]"
th:value="$ {status.code}" th:text="${status.name}"></option>
</select>
</li>
通过引用订单状态的枚举集合变量statuses,使用一个th:each循环语句,即可生成一个订单状态的下拉列表框。
订单状态修改设计
下面再来看看订单状态的修改设计。在控制器OrderController 的设计中,使用如下所示的实现方法:
@RestController
@RequestMapping ("/order")
public class OrderController {
private static Logger logger =
LoggerFactory.getLogger (0rderController.class);
@Autowired
private OrderRestService orderRestService;
@GetMapping (" /edit/{id}")
public ModelAndView update (@PathVariable String id, HttpServletRequestregquest, Mode lMap model){
String json = orderRestService.findById(id);
OrderQo orderQo = new Gson().fromJson(json, OrderQo.class);
//订单状态枚举集合
StatusEnum[I] statuses = StatusEnum.values();
model.addAttribute( "statuses", statuses);
model .addAttribute ( "order", orderQo);
return new ModelAndView("order/edit");
}
CPostMapping(value="/update")
public String update (0rderQo orderQo,HttpServletRequest request)[
String json = orderRestService.findById(orderQo.getId());
OrderQo newOrder = new Gson().fromJson(json, OrderQo.class);
newOrder.setStatus (orderQo.getStatus());
neworder.setModify (new Date());
orderRestService. update (newOrder);
logger.info("修改=" +orderQo.getId());return "1";
}
}
这里,先从OrderRestService 中取得一个订单数据,然后在页面edit.html 中展示出来。当用户在页面上选择一个订单状态并提交之后,就会调用OrderRestService的update方法,请求数据库更新数据。
在页面edit.html的设计中,使用了一个弹出窗口,其完整代码如下所示:
<html xmlns:th="http://www.thymeleaf.org">
<script th:src="@{/scripts/order/edit.js}"></script><form id="saveForm" method-"post">
<input type="hidden" name="id" id="id" th:value="$ {order.id} "/><div class="addInfBtn">
<h3 class="itemTit"><span>子类信息</span></h3><table class="addNewInfList">
<tr>
<th>订单号</th><td width="200">
<input class="inp-list w-200 clear-mr f-left" type="text"
th:value="${order.orderNo}" readonly="true" id="orderNo" name="orderNo"maxlength="16"/>
</td>
<th>状态</th><td>
<select id="status" name="status">
<option th:each="status:${statuses]"
th:value="${status.code] "
th:text="${status.name } "
th:selected="${status.code == order.status}"
>/option>
</select>
</td>
</tr>
</table>
<div class="bottomBtnBox">
<a class="btn-93x38 saveBtn" href="javascript:void(0)">确定</a>
<a class="btn-93x38 backBtn" href="javascript:closeDialog (0)">返回</a></div>
</div>
</form>
其中,订单编号设定为只读状态,即不能被修改,而订单的状态使用一个下拉列表框来显示。刚打开页面时,原有的订单状态会处于已经选定的状态。这样当用户在页面上选择另一个状态进行提交时,就可以对订单状态进行修改操作了。
在开发完成之后,需要进行一个集成测试。在这个集成测试中,会用到消息队列,所以必须保证 RabbitMQ服务器已经启动,并且程序与服务器的连接配置都正确无误。
按下列顺序启动各个微服务模块:
(1)库存管理微服务API应用:goods-restapi。(2)订单微服务应用接口设计: order-restapi。(3)订单后台管理应用: order-web。
上面各个模块启动成功之后,通过浏览器打开如下链接地址,即订单Web应用的后台管理首页:
http: //Localhost:8095
如果打开成功,并且已有订单数据,则可以看到如图8-3所示页面。
现在我们编辑一下订单,选择图8-3中第一个已付款的订单,单击“编辑”选项,在弹出的编辑窗口中,将状态修改为已撤销,如图8-4 所示。
单击“确定”按钮,如果页面上返回了编辑成功的提示,则说明修改操作已经完成。查看订单接口和库存接口的控制台输出日志,看看分布式事务的消息是否已经处理完成。如果一切正常,则可以在订单接口的控制台中看到如下所示的输出日志:
MessageSender -发送订单变更消息:
("id":"5d6b777971d7ad663e4ac3dd" , "orderNo": "1567324025207", "userid":11111235,"merchantid":123456,"amount":11.2,"status":-1, "created" :"Sep 1, 2019 3:47:05PM","modify" : "Sep 1,20193:50:27
PM", "orderDetails":[{ "goodsid":1, "goodsname":"测试商品
1", "photo": " /images/demol.png" , "nums " : 1 , "price":11.2,"money":11.21], "page":0,"size" :10}
如果能看到上面所示的日志,则说明订单状态变更的消息已经发送到了消息队列上。
再切换到库存接口的控制台,查看库存接口的输出日志。如果能看到如下所示的输出日志,则说明消息已经处理成功,同时也说明分布式事务已经处理完毕。
接收到订单更新消息,订单编号=1567324025207
...
更新了商品购买数量,商品名称=测试商品1
...
这时,在订单管理后台的首页上,可以看到订单的状态已修改成功,如图8-5所示。在图8-5中,我们还可以按各种查询条件和不同的参数,实现各种不同需求的分页数据的列表查询操作。
本章我们使用MongoDB开发了一个可以支持海量数据的订单系统,并且使用Spring 5的反应式编程设计,实现了支持非阻塞异步调用的高并发微服务订单接口,所以这是一个高性能的订单微服务应用系统。有关反应式编程设计,由于其异步调用的特性,使得其只能支持无事务管理的数据库设计。而对于微服务设计来说,其本身就是一种分布式的应用,所以有关事务管理的设计,只能使用分布式的事务管理来实现。在本章订单状态变更所引起的事务管理实例中,我们使用消息队列实现了分布式事务管理中数据最终一致性的设计。
SpringCloud微服务架构实战:海量订单系统微服务开发,订单接口微服务开发、订单的分布式事务管理、 订单管理后台微服务开发、集成测试;
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。