javascript
Spring Cloud【Finchley】实战-02订单微服务
文章目錄
- Spring Cloud【Finchley】專欄
- 概述
- 數據模型-訂單微服務
- API
- 業務邏輯分析
- 搭建訂單微服務
- 依賴及配置文件
- pom.xml
- application.yml
- 將微服務注冊到注冊中心
- 實體類
- Order
- OrderDetail
- Dao層
- OrderRepository
- OrderDetailRepository
- 單元測試
- Service層
- Order 和OrderDetail 合并為一個DTO對象
- OrderService接口和實現類
- Controller層
- 測試
- 知識點總結
- Gson庫
- 將Json轉換為對象
- 解析json數組
- 將Java對象轉換為Json
- Github
Spring Cloud【Finchley】專欄
如果還沒有系統的學過Spring Cloud ,先到我的專欄去逛逛吧
Spring Cloud 【Finchley】手札
概述
這里我們簡單的說下業務相關的需求,重點是體會微服務這種理念是如何落地的。
數據模型-訂單微服務
通常來講,微服務都是分數據庫的。這里我們新建個數據庫給訂單微服務 ,數據庫實例名 o2o-order
-- ---------------------------- -- Table structure for order -- ---------------------------- -- 訂單 create table `artisan_order` (`order_id` varchar(32) not null,`buyer_name` varchar(32) not null comment '買家名字',`buyer_phone` varchar(32) not null comment '買家電話',`buyer_address` varchar(128) not null comment '買家地址',`buyer_openid` varchar(64) not null comment '買家微信openid',`order_amount` decimal(8,2) not null comment '訂單總金額',`order_status` tinyint(3) not null default '0' comment '訂單狀態, 默認為新下單',`pay_status` tinyint(3) not null default '0' comment '支付狀態, 默認未支付',`create_time` timestamp not null default current_timestamp comment '創建時間',`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',primary key (`order_id`),key `idx_buyer_openid` (`buyer_openid`) );-- ---------------------------- -- Table structure for order_detail -- ---------------------------- -- 訂單詳情 create table `order_detail` (`detail_id` varchar(32) not null,`order_id` varchar(32) not null,`product_id` varchar(32) not null,`product_name` varchar(64) not null comment '商品名稱',`product_price` decimal(8,2) not null comment '當前價格,單位分',`product_quantity` int not null comment '數量',`product_icon` varchar(512) comment '小圖',`create_time` timestamp not null default current_timestamp comment '創建時間',`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',primary key (`detail_id`),key `idx_order_id` (`order_id`) );訂單與訂單詳情是一對多的關系,一個訂單中可能包含多個訂單詳情,比如我下一個訂單,這個訂單中買了1杯奶茶、2杯可樂等。
order_detail中不僅設計了product_id,同時也冗余了 product_name product_price product_icon等,主要是考慮到有些促銷活動這些字段會經常更改這些因素。
API
請求:
POST方式 /order/create
內容:
"name": "xxx","phone": "xxxx","address": "xxxx","openid": "xxxx", //用戶的微信openid"items": [{"productId": "xxxxxx","productQuantity": 2 //購買數量}]后端盡量少依賴前端傳遞的數據,為了安全起見,產品相關的數據,只傳遞了一個productId和productQuantity,而沒有將價格、描述等等一并傳遞,不傳遞就不會被篡改,也減少了交互數據的大小。
返回:
{"code": 0,"msg": "成功","data": {"orderId": "123456" } }業務邏輯分析
搭建訂單微服務
依賴及配置文件
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.artisan</groupId><artifactId>artisan_order</artifactId><version>0.0.1-SNAPSHOT</version><name>artisan_order</name><description>Order</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Finchley.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>application.yml
這里我們連接到 order微服務的數據庫。 我這里本地環境,就新建了個數據庫實例。
server:port: 8081spring:application:name: artisan-order# datasourcedatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/o2o-order?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: root#jpajpa:show-sql: true # Eureka eureka:client:service-url:defaultZone: http://localhost:8761/eureka/將微服務注冊到注冊中心
application.yml中配置了Eureka的信息后,我們在啟動類增加@EnableEurekaClient即可
啟動注冊中心微服務,啟動該服務
訪問 http://localhost:8761/
注冊成功
實體類
Order
package com.artisan.order.domain;import lombok.Data;import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import java.math.BigDecimal; import java.util.Date;@Data // 必不可少 @Entity @Table(name = "artisan_order") public class Order {/*** 訂單id.*/@Idprivate String orderId;/*** 買家名字.*/private String buyerName;/*** 買家手機號.*/private String buyerPhone;/*** 買家地址.*/private String buyerAddress;/*** 買家微信Openid.*/private String buyerOpenid;/*** 訂單總金額.*/private BigDecimal orderAmount;/*** 訂單狀態, 默認為0新下單.*/private Integer orderStatus;/*** 支付狀態, 默認為0未支付.*/private Integer payStatus;/*** 創建時間.*/private Date createTime;/*** 更新時間.*/private Date updateTime; }OrderDetail
package com.artisan.order.domain;import lombok.Data;import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import java.math.BigDecimal;@Data// 必不可少 @Entity // 如果實體類是OrderDetail,表名是order_detail,則這個注解可省略 @Table(name = "order_detail") public class OrderDetail {// 必不可少@Idprivate String detailId;/*** 訂單id.*/private String orderId;/*** 商品id.*/private String productId;/*** 商品名稱.*/private String productName;/*** 商品單價.*/private BigDecimal productPrice;/*** 商品數量.*/private Integer productQuantity;/*** 商品小圖.*/private String productIcon; }Dao層
創建訂單無非就是往這兩個表里寫入數據。直接利用jpa提供的save方法即可。
OrderRepository
空實現 ,利用jpa本身提供的save方法
package com.artisan.order.repository;import com.artisan.order.domain.Order; import org.springframework.data.jpa.repository.JpaRepository;// JpaRepository<Order,String> 第一個是要操作的對象,第二個是實體類中標注的@Id的字段的類型 (主鍵類型) public interface OrderRepository extends JpaRepository<Order,String> {}OrderDetailRepository
空實現 ,利用jpa本身提供的save方法
package com.artisan.order.repository;import com.artisan.order.domain.OrderDetail; import org.springframework.data.jpa.repository.JpaRepository;public interface OrderDetailRepository extends JpaRepository<OrderDetail ,String> { }單元測試
OrderRepositoryTest
package com.artisan.order.repository;import com.artisan.order.ArtisanOrderApplicationTests; import com.artisan.order.domain.Order; import com.artisan.order.enums.OrderStatusEnum; import com.artisan.order.enums.PayStatusEnum; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import java.math.BigDecimal;@Component public class OrderRepositoryTest extends ArtisanOrderApplicationTests {@Autowiredprivate OrderRepository orderRepository;@Testpublic void testSave(){Order order = new Order();order.setOrderId("1222");order.setBuyerName("artisan");order.setBuyerPhone("123445664");order.setBuyerAddress("Artisan Tech");order.setBuyerOpenid("11112233");order.setOrderAmount(new BigDecimal(3.9));order.setOrderStatus(OrderStatusEnum.NEW.getCode());order.setPayStatus(PayStatusEnum.WAIT.getCode());Order result = orderRepository.save(order);Assert.assertNotNull(result);} }數據庫記錄
package com.artisan.order.repository;import com.artisan.order.ArtisanOrderApplicationTests; import com.artisan.order.domain.OrderDetail; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import java.math.BigDecimal;@Component public class OrderDetailRepositoryTest extends ArtisanOrderApplicationTests {@Autowiredprivate OrderDetailRepository orderDetailRepository;@Testpublic void testSave() {OrderDetail orderDetail = new OrderDetail();orderDetail.setDetailId("1111");orderDetail.setOrderId("111111");orderDetail.setProductIcon("http://xxx.com");orderDetail.setProductId("22222");orderDetail.setProductName("拿鐵");orderDetail.setProductPrice(new BigDecimal(0.01));orderDetail.setProductQuantity(2);OrderDetail result = orderDetailRepository.save(orderDetail);Assert.assertTrue(result != null);} }
單元測試 通過。
Service層
分析下,我們要往artisan_order 和 order_detail中寫入數據,肯定要傳入Order和OrderDetail實體類,類似于 createOrder(Order order , OrderDetail orderDetail) ,根據業務規則,一個Order中可能有多個OrderDetail, 所以入參OrderDetail 必須是個集合,并且返回結果也不好定義。 因此我們將這倆合并一下,封裝成DTO來使用,作為入參和返回結果。
Order 和OrderDetail 合并為一個DTO對象
下圖中類上少兒個注解 @Data,注意補上
OrderService接口和實現類
package com.artisan.order.service;import com.artisan.order.dto.OrderDTO;public interface OrderService {OrderDTO createOrder(OrderDTO orderDTO); }我們來分析下前端的請求
"name": "xxx","phone": "xxxx","address": "xxxx","openid": "xxxx", //用戶的微信openid"items": [{"productId": "xxxxxx","productQuantity": 2 //購買數量}]結合業務邏輯
-
校驗前臺入參
-
查詢商品信息(調用商品微服務)
-
計算訂單總價
-
扣減庫存(調用商品微服務)
-
訂單入庫
逐一分析下目前的可行性
-
參數校驗,我們放在Controller層校驗,所以Service層這里不寫
-
調用微服務的,我們目前還不具備,沒法做
-
計算訂單總價,前臺入參僅僅傳了ProductId, 而Product的數據需要調用商品微服務,目前沒法做
-
訂單入庫,其實分兩部分,第一個是artisan_order表,第二個是Order_detail表。 order_detail表包含了Product的內容,目前也是做不了。
綜合分析,目前在Service層能做的僅僅是 入庫artisan_order表
那在實現類里,我們先實現部分吧
package com.artisan.order.service.impl;import com.artisan.order.domain.Order; import com.artisan.order.dto.OrderDTO; import com.artisan.order.enums.OrderStatusEnum; import com.artisan.order.enums.PayStatusEnum; import com.artisan.order.repository.OrderDetailRepository; import com.artisan.order.repository.OrderRepository; import com.artisan.order.service.OrderService; import com.artisan.order.utils.KeyUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;import java.math.BigDecimal;@Service public class OrderServiceImpl implements OrderService {@AutowiredOrderRepository orderRepository;@AutowiredOrderDetailRepository orderDetailRepository;@Overridepublic OrderDTO createOrder(OrderDTO orderDTO) {// TODO 查詢商品信息(調用商品微服務)// TODO 計算訂單總價// TODO 扣減庫存(調用商品微服務)//訂單入庫Order order = new Order();orderDTO.setOrderId(KeyUtil.genUniqueKey());// 復制屬性BeanUtils.copyProperties(orderDTO, order);// 設置其他屬性order.setOrderAmount(new BigDecimal("100")); // TODO 后需要修改order.setOrderStatus(OrderStatusEnum.NEW.getCode());order.setPayStatus(PayStatusEnum.WAIT.getCode());orderRepository.save(order);return orderDTO;} }Controller層
這里僅列出關鍵代碼,其余請參考github
package com.artisan.order.form;import lombok.Data; import org.hibernate.validator.constraints.NotEmpty;import java.util.List;@Data public class OrderForm {/*** 對應** {* "name": "xxx",* "phone": "xxxx",* "address": "xxxx",* "openid": "xxxx", //用戶的微信openid* "items": [* {* "productId": "xxxxxx",* "productQuantity": 2 //購買數量* }* ]* }****//*** 買家姓名*/@NotEmpty(message = "姓名必填")private String name;/*** 買家手機號*/@NotEmpty(message = "手機號必填")private String phone;/*** 買家地址*/@NotEmpty(message = "地址必填")private String address;/*** 買家微信openid*/@NotEmpty(message = "openid必填")private String openid;/*** 購物車*/@NotEmpty(message = "購物車不能為空")private String items;} package com.artisan.order.converter;import com.artisan.order.domain.OrderDetail; import com.artisan.order.dto.OrderDTO; import com.artisan.order.enums.ResultEnum; import com.artisan.order.exception.OrderException; import com.artisan.order.form.OrderForm; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j;import java.util.ArrayList; import java.util.List;@Slf4j public class OrderForm2OrderDTOConverter {public static OrderDTO convert(OrderForm orderForm) {Gson gson = new Gson();OrderDTO orderDTO = new OrderDTO();orderDTO.setBuyerName(orderForm.getName());orderDTO.setBuyerPhone(orderForm.getPhone());orderDTO.setBuyerAddress(orderForm.getAddress());orderDTO.setBuyerOpenid(orderForm.getOpenid());List<OrderDetail> orderDetailList = new ArrayList<>();try {// fromJson 從Json相關對象到Java實體的方法 ,轉換成列表類型orderDetailList = gson.fromJson(orderForm.getItems(),new TypeToken<List<OrderDetail>>() {}.getType());}catch(Exception e){log.error("【json轉換】錯誤, string={}", orderForm.getItems());throw new OrderException(ResultEnum.PARAM_ERROR);}orderDTO.setOrderDetailList(orderDetailList);return orderDTO;} } package com.artisan.order.controller;import com.artisan.order.converter.OrderForm2OrderDTOConverter; import com.artisan.order.dto.OrderDTO; import com.artisan.order.enums.ResultEnum; import com.artisan.order.exception.OrderException; import com.artisan.order.form.OrderForm; import com.artisan.order.service.OrderService; import com.artisan.order.vo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;import javax.validation.Valid; import java.util.HashMap; import java.util.Map;@RestController @RequestMapping("/order") @Slf4j public class OrderController {@AutowiredOrderService orderService;@PostMapping("/create")public Result create(@Valid OrderForm orderForm,BindingResult bindingResult) {if (bindingResult.hasErrors()){log.error("【Create Order】參數不正確, orderForm={}", orderForm);throw new OrderException(ResultEnum.PARAM_ERROR.getCode(),bindingResult.getFieldError().getDefaultMessage());}// orderForm -> orderDTOOrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {log.error("【Create Order】購物車信息為空");throw new OrderException(ResultEnum.CART_EMPTY);}OrderDTO result = orderService.createOrder(orderDTO);Map<String, String> map = new HashMap<>();map.put("orderId", result.getOrderId());return Result.success(map);}}測試
使用PostMan
查看數據庫
OK
說明下: x-www-form-urlencoded 這種格式 就是application/x-www-from-urlencoded,會將表單內的數據轉換為鍵值對,比如:name=artisan&phone=123
知識點總結
Gson庫
谷歌提供的 JSON – Java Object 相互轉換的 Java序列化/反序列化庫。
將Json轉換為對象
解析json數組
orderDetailList = gson.fromJson(orderForm.getItems(),new TypeToken<List<OrderDetail>>() {}.getType());更多示例參考
將Java對象轉換為Json
Github
https://github.com/yangshangwei/springcloud-o2o/tree/master/artisan-product
總結
以上是生活随笔為你收集整理的Spring Cloud【Finchley】实战-02订单微服务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Cloud【Finchle
- 下一篇: Spring Cloud【Finchle