javascript
Spring Cloud【Finchley】实战-01注册中心及商品微服务
文章目錄
- Spring Cloud【Finchley】專欄
- 概述
- 版本說明
- 搭建Eureka Server注冊中心
- 工程結構
- Step1. pom添加依賴
- Step2.application.yml 配置Eureka的信息
- Step3. 啟動類增加@EnableEurekaServer
- Step4 啟動測試
- Github地址
- 數據模型-商品微服務
- Product 微服務構建
- 新建工程作為 Eureka Client,注冊到Eureka Server上
- Step1. pom.xml 添加依賴
- Step2 啟動類增加@EnableEurekaClient注解
- Step3 .啟動 驗證
- API-約定前后臺數據交互格式
- pom.xml引入依賴
- 配置文件增加數據庫配置
- 實體類 Product
- Dao層 ProductRepository
- 單元測試
- 實體類 ProductCategory
- Service層
- ProductService 接口
- ProductService 接口實現類
- ProductStatusEnum
- 對接口進行單元測試
- ProductCategoryService 接口
- ProductCategoryService 接口實現類
- 單元測試
- Controller層
- VO封裝
- ResultVO 前后臺交互的統一格式模板
- ProductVO :返回給前臺的商品信息格式,包含目錄信息
- ProductInfoVO 具體產品的數據VO
- Controller層邏輯
- 啟動測試
- 知識點總結
- Java8的Stream
- Beanutils.copyProperties( )
- Github地址
Spring Cloud【Finchley】專欄
如果還沒有系統的學過Spring Cloud ,先到我的專欄去逛逛吧
Spring Cloud 【Finchley】手札
概述
點餐系統,重點體會使用Spring Cloud微服務組件如何拆分系統
優秀的系統都是演進而來的,不要害怕出錯,大膽折騰吧。
我們先來針對商品微服務進行設計和構建
版本說明
- spring boot : 2.0.3.RELEASE
- spring cloud: Finchley.RELEASE
搭建Eureka Server注冊中心
如果沒有了解過Eureka ,建議先學習下
Spring Cloud【Finchley】-02服務發現與服務注冊Eureka + Eureka Server的搭建
Spring Cloud【Finchley】-13 Eureka Server HA高可用 2個/3個節點的搭建及服務注冊調用
工程結構
Step1. pom添加依賴
<?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><groupId>com.artisan</groupId><artifactId>eureka-server</artifactId><version>0.0.1-SNAPSHOT</version><name>eureka-server</name><description>eureka server</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</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>Step2.application.yml 配置Eureka的信息
# app name spring:application:name: eureka-server# 啟動端口 server:port: 8761# 單節點的eureka (后續會改成集群模式) eureka:client:# 是否將自己注冊到Eureka Server ,默認為true.因為當前應用是作為Eureka Server用,因此設置為falseregister-with-eureka: false# eureka.client.fetch-registry:是否從Eureka Server獲取注冊信息,默認為true.# 因為我們這里目前是個單節點的Eureka Server ,不需要與其他的Eureka Server節點的數據,因此設為falsefetch-registry: false# 置與Eureka Server交互的地址,查詢服務和注冊服務都依賴這個地址。# 默認為 http://localhost:8761/eureka ,多個地址可使用 , 分隔service-url:defaultZone: http://localhost:8761/eurekaStep3. 啟動類增加@EnableEurekaServer
package com.artisan.eurekaserver;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication @EnableEurekaServer public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}}Step4 啟動測試
啟動application,訪問 http://localhost:8761/
Github地址
https://github.com/yangshangwei/springcloud-o2o/tree/master/eureka-server
數據模型-商品微服務
我們先來整理商品微服務模塊的庫表設計。
- 商品目錄
- 商品
商品要歸屬于某個商品目錄,我們通過在category_type字段來將產品product和產品目錄product_category關聯起來。
-- ---------------------------- -- Table structure for product_category -- ---------------------------- DROP TABLE IF EXISTS `product_category`; CREATE TABLE `product_category` (`category_id` int(11) NOT NULL AUTO_INCREMENT,`category_name` varchar(255) DEFAULT NULL COMMENT '產品目錄名稱',`category_type` int(11) NOT NULL COMMENT '產品目錄類型,用于存儲特定類型的商品',`create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`category_id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;-- ---------------------------- -- Records of product_category -- ---------------------------- INSERT INTO `product_category` VALUES ('1', '熱飲', '99', '2019-03-20 22:47:41', '2019-03-20 22:47:41'); INSERT INTO `product_category` VALUES ('2', '酒水', '98', '2019-03-20 22:48:13', '2019-03-20 22:48:13'); INSERT INTO `product_category` VALUES ('3', '甜品', '97', '2019-03-20 22:47:51', '2019-03-20 22:47:51'); -- ---------------------------- -- Table structure for product -- ---------------------------- DROP TABLE IF EXISTS `product`; CREATE TABLE `product` (`product_id` int(11) NOT NULL AUTO_INCREMENT,`product_name` varchar(255) NOT NULL,`product_stock` int(11) NOT NULL COMMENT '庫存',`product_price` decimal(8,2) DEFAULT NULL,`product_description` varchar(255) DEFAULT NULL,`product_icon` varchar(255) DEFAULT NULL,`product_status` tinyint(3) DEFAULT '0' COMMENT '商品狀態, 0正常 1下架',`category_type` int(11) DEFAULT NULL COMMENT '產品目錄',`create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`product_id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;-- ---------------------------- -- Records of product -- ---------------------------- INSERT INTO `product` VALUES ('1', '拿鐵咖啡', '99', '20.99', '咖啡,提神醒腦', null, '0', '99', '2019-03-20 22:49:47', '2019-03-20 22:49:50'); INSERT INTO `product` VALUES ('2', '青島純生', '200', '7.50', '啤酒', null, '0', '98', '2019-03-20 22:50:48', '2019-03-20 22:50:55'); INSERT INTO `product` VALUES ('3', '卡布奇諾', '87', '15.00', '卡布奇諾的香味', null, '0', '99', '2019-03-20 22:51:53', '2019-03-20 22:51:56');Product 微服務構建
新建工程作為 Eureka Client,注冊到Eureka Server上
Step1. pom.xml 添加依賴
<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>Step2 啟動類增加@EnableEurekaClient注解
package com.artisan.product;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication @EnableEurekaClient public class ArtisanProductApplication {public static void main(String[] args) {SpringApplication.run(ArtisanProductApplication.class, args);}}Step3 .啟動 驗證
先啟動eureka-server這個服務,然后啟動 artisan-product這個服務。訪問eureka的地址 http://localhost:8761/
說明成功注冊到了Eureka Server上
API-約定前后臺數據交互格式
請求Get方式 - /product/list
返回:
{"code":0,"msg":"成功","data":[{"name":"商品目錄名稱","type":"商品目錄類型","product":[{"id":"商品id","name":"商品名稱","price": 100,"description":"商品描述","icon":"商品圖片地址"}]}] }[] 表示數組,可以返回多條
約定查詢 只查詢上架的商品。
分析上述格式,結合我們的數據模型,可知會涉及到商品目錄及商品兩個表。
pom.xml引入依賴
- 持久層使用 spring-data-jpa
- 數據庫使用mysql
- 為了簡化代碼,引入了lombok (IDEA記得安裝lombok插件)
配置文件增加數據庫配置
server:port: 8080spring:application:name: artisan-product# datasourcedatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/o2o?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: root# jpa 輸出sqljpa:show-sql: true # Eureka eureka:client:service-url:defaultZone: http://localhost:8761/eureka/實體類 Product
庫表建好了,那接下來就要建立和庫表對應的實體類了
package com.artisan.product.domain;import lombok.Data;import javax.persistence.*; import java.math.BigDecimal; import java.util.Date;// lombok @Data// @Table指定這個實體對應數據庫的表名 // product_info ProductInfo這種格式的可以省略不寫 ,如果 實體類叫product , 表名叫t_product 那么就要顯式指定了 @Table(name = "product")// @Entity表示這個類是一個實體類 @Entity public class Product {// @Id標識主鍵 及主鍵生成策略 @Id@GeneratedValue(strategy = GenerationType.IDENTITY)private String productId;private String productName;private Integer productStock;private BigDecimal productPrice;private String productDescription;private String productIcon;private Integer productStatus;private Integer categoryType;private Date createTime;private Date updateTime;}Dao層 ProductRepository
接口, 繼承 JpaRepository<T, ID>
package com.artisan.product.repository;import com.artisan.product.domain.Product; import org.springframework.data.jpa.repository.JpaRepository;import java.util.List;/*** JpaRepository<Product, String> 第一個參數為具體的domain對象,第二個參數為主鍵類型*/ public interface ProductRepository extends JpaRepository<Product, String> {// 根據產品狀態查詢產品List<Product> findByProductStatus(Integer productStatus); }單元測試
在ProductRepository 中 右鍵 – Go To --Test --Create New Test 新建個單元測試
Spring Boot的單元測試別忘了這倆注解
@RunWith(SpringRunner.class)
@SpringBootTest
或者繼承ArtisanProductApplicationTests ,加上@Component注解
@Component public class ProductCategoryServiceImplTest extends ArtisanProductApplicationTests package com.artisan.product.repository;import com.artisan.product.domain.Product; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;import java.util.List;import static org.junit.Assert.*;@RunWith(SpringRunner.class) @SpringBootTest public class ProductRepositoryTest {@AutowiredProductRepository productRepository;@Testpublic void findByProductStatus() {List<Product> list = productRepository.findByProductStatus(0);Assert.assertEquals(3,list.size());} }結合數據庫中的數據
實體類 ProductCategory
過程同上,這里不贅述了 ,代碼如下
domain實體類
package com.artisan.product.domain;import lombok.Data;import javax.persistence.*; import java.util.Date;@Data @Table(name = "product_category") @Entity public class ProductCategory {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private String categoryId;private String categoryName;private Integer categoryType;private Date createTime;private Date updateTime;}Dao接口
package com.artisan.product.repository;import com.artisan.product.domain.ProductCategory; import org.springframework.data.jpa.repository.JpaRepository;import java.util.List;public interface ProductCategoryRepository extends JpaRepository<ProductCategory, String> {List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); }單元測試
package com.artisan.product.repository;import com.artisan.product.domain.ProductCategory; import com.netflix.discovery.converters.Auto; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;import java.util.Arrays; import java.util.List;import static org.junit.Assert.*;@RunWith(SpringRunner.class) @SpringBootTest public class ProductCategoryRepositoryTest {@Autowiredprivate ProductCategoryRepository productCategoryRepository;@Testpublic void findByCategoryTypeIn() {List<ProductCategory> list = productCategoryRepository.findByCategoryTypeIn(Arrays.asList(99, 98, 97));Assert.assertEquals(3,list.size());} }Service層
ProductService 接口
package com.artisan.product.service;import com.artisan.product.domain.Product;import java.util.List;public interface ProductService {// 查詢上架商品List<Product> getAllUpProduct(); }ProductService 接口實現類
package com.artisan.product.service.impl;import com.artisan.product.domain.Product; import com.artisan.product.enums.ProductStatusEnum; import com.artisan.product.repository.ProductRepository; import com.artisan.product.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;import java.util.List;@Service public class ProductServiceImpl implements ProductService {@Autowiredprivate ProductRepository productRepository;@Overridepublic List<Product> getAllUpProduct() {return productRepository.findByProductStatus(ProductStatusEnum.UP.getCode());} }ProductStatusEnum
為了方便,將狀態封裝到了Enum中
package com.artisan.product.enums;import lombok.Getter;@Getter public enum ProductStatusEnum {UP(0,"上架"),DOWN(1,"下架");private int code ;private String msg;ProductStatusEnum(int code, String msg){this.code = code;this.msg = msg;} }對接口進行單元測試
package com.artisan.product.service;import com.artisan.product.ArtisanProductApplicationTests; import com.artisan.product.domain.Product; import com.artisan.product.enums.ProductStatusEnum; import com.artisan.product.repository.ProductRepository; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import java.util.List;import static org.junit.Assert.*;@Component public class ProductServiceTest extends ArtisanProductApplicationTests {@Autowiredprivate ProductRepository productRepository;@Testpublic void getAllUpProduct() {List<Product> list = productRepository.findByProductStatus(ProductStatusEnum.UP.getCode());Assert.assertEquals(3,list.size());} }ProductCategoryService 接口
package com.artisan.product.service;import com.artisan.product.domain.ProductCategory;import java.util.List;public interface ProductCategoryService {List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); }ProductCategoryService 接口實現類
package com.artisan.product.service.impl;import com.artisan.product.domain.ProductCategory; import com.artisan.product.repository.ProductCategoryRepository; import com.artisan.product.service.ProductCategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;import java.util.List;@Service public class ProductCategoryServiceImpl implements ProductCategoryService {@Autowiredprivate ProductCategoryRepository productCategoryRepository;@Overridepublic List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) {return productCategoryRepository.findByCategoryTypeIn(categoryTypeList);}}單元測試
package com.artisan.product.service.impl;import com.artisan.product.ArtisanProductApplicationTests; import com.artisan.product.domain.ProductCategory; import com.artisan.product.repository.ProductCategoryRepository; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import java.util.Arrays; import java.util.List;import static org.junit.Assert.*;@Component public class ProductCategoryServiceImplTest extends ArtisanProductApplicationTests {@Autowiredprivate ProductCategoryRepository productCategoryRepository;@Testpublic void findByCategoryTypeIn() {List<ProductCategory> list = productCategoryRepository.findByCategoryTypeIn(Arrays.asList(99,98,97));Assert.assertEquals(3,list.size());} }Controller層
先來觀察下,返回給前端的數據
code , msg , 泛型的data 是最外層的數據,那封裝下吧 。 可以理解為也是一個VO(View Object)對象,包含3個節點(code msg 泛型的data)
同時data節點 [] ,自然是個數組了,可包含多個{}對象。
按照上圖的劃分,也把這些信息封裝成VO吧。
為了避免引起誤解,我們把 改為products .
VO封裝
ResultVO 前后臺交互的統一格式模板
package com.artisan.product.vo;import lombok.Getter;@Getter public class Result<T> {private Integer code ;private String msg ;private T data;/*** 成功時候的調用* */public static <T> Result<T> success(T data){return new Result<T>(data);}private Result(T data) {this.code = 0;this.msg = "success";this.data = data;}/*** 失敗時候的調用* */public static <T> Result<T> error(ErrorCodeMsg cm){return new Result<T>(cm);}private Result(ErrorCodeMsg cm) {if(cm == null) {return;}this.code = cm.getCode();this.msg = cm.getMsg();} }用到了 ErrorCodeMsg
package com.artisan.product.vo;import lombok.Getter;@Getter public class ErrorCodeMsg {private int code;private String msg;// 異常public static ErrorCodeMsg SERVER_ERROR = new ErrorCodeMsg(-1, "服務端異常");private ErrorCodeMsg(int code, String msg) {this.code = code;this.msg = msg;}}ProductVO :返回給前臺的商品信息格式,包含目錄信息
package com.artisan.product.vo;import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data;import java.util.List;@Data public class ProductVO {// @JsonProperty注解用于屬性上,作用是把該屬性的名稱序列化為另外一個名稱,// 如把categoryName屬性序列化為name// 【這里約定給前臺返回的節點名為name, 但是為了方便理解這個name到底是什么的name,在vo中定義了方便理解的屬性名】@JsonProperty("name")private String categoryName;@JsonProperty("type")private Integer categoryType;// 因為這個節點下可能返回多個ProductInfoVO,因此定義一個List集合@JsonProperty("products")private List<ProductInfoVO> productInfoVOList ;}ProductInfoVO 具體產品的數據VO
package com.artisan.product.vo;import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data;import java.math.BigDecimal;@Data public class ProductInfoVO {@JsonProperty("id")private String productId;@JsonProperty("name")private String productName;@JsonProperty("price")private BigDecimal productPrice;@JsonProperty("description")private String productDescription;@JsonProperty("icon")private String productIcon; }Controller層邏輯
分析約定的前后臺交互的JSON格式:
- 每個ProductVO中我們需要獲取產品目錄名稱及產品目錄的category_type ,
調用ProductCategoryService#categoryService方法即可。 - categoryService的入參為categoryTypeList,因此需要調用ProductService#getAllUpProduct獲取所有上架商品對應的categoryType.
- 獲取到了后臺的數據后,按照約定的格式拼裝返回JSON串即可
啟動測試
訪問 http://localhost:8080/product/list
{"code": 0,"msg": "success","data": [{"name": "熱飲","type": 99,"products": [{"id": "1","name": "拿鐵咖啡","price": 20.99,"description": "咖啡,提神醒腦","icon": null},{"id": "3","name": "卡布奇諾","price": 15,"description": "卡布奇諾的香味","icon": null}]},{"name": "酒水","type": 98,"products": [{"id": "2","name": "青島純生","price": 7.5,"description": "啤酒","icon": null}]}] }格式化看下更加直觀:
知識點總結
Java8的Stream
//2. 獲取類目type列表List<Integer> categoryTypeList = productInfoList.stream().map(Product::getCategoryType).collect(Collectors.toList());使用Java8中的Stream可以方便的對集合對象進行各種便利、高效的聚合操作,或者大批量數據操作。
map生成的是個一對一映射,相當于for循環
注意:流只能使用一次,使用結束之后,這個流就無法使用了。
點擊查看更多示例
Beanutils.copyProperties( )
// 將屬性copy到productInfoVO,避免逐個屬性set,更簡潔BeanUtils.copyProperties(product, productInfoVO);org.springframework.beans.BeanUtils# copyProperties作用是將一個Bean對象中的數據封裝到另一個屬性結構相似的Bean對象中。
同時org.apache.commons.beanutils.BeanUtils也有個copyProperties
需要注意的是這倆的copyProperties方法參數位置不同
org.springframework.beans.BeanUtils#copyProperties(sourceDemo, targetDemo)org.apache.commons.beanutils.BeanUtils#copyProperties(targetDemo, sourceDemo)Github地址
https://github.com/yangshangwei/springcloud-o2o/tree/master/artisan-product
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Spring Cloud【Finchley】实战-01注册中心及商品微服务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nginx-从零开始使用nginx实现反
- 下一篇: Spring Cloud【Finchle