失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Spring Cloud【Finchley】实战-01注册中心及商品微服务

Spring Cloud【Finchley】实战-01注册中心及商品微服务

时间:2024-06-22 09:59:55

相关推荐

Spring Cloud【Finchley】实战-01注册中心及商品微服务

文章目录

Spring Cloud【Finchley】专栏概述版本说明搭建Eureka Server注册中心工程结构Step1. pom添加依赖Step2.application.yml 配置Eureka的信息Step3. 启动类增加@EnableEurekaServerStep4 启动测试Github地址数据模型-商品微服务Product 微服务构建新建工程作为 Eureka Client,注册到Eureka Server上Step1. pom.xml 添加依赖Step2 启动类增加@EnableEurekaClient注解Step3 .启动 验证API-约定前后台数据交互格式pom.xml引入依赖配置文件增加数据库配置实体类 ProductDao层 ProductRepository单元测试实体类 ProductCategoryService层ProductService 接口ProductService 接口实现类ProductStatusEnum对接口进行单元测试ProductCategoryService 接口ProductCategoryService 接口实现类单元测试Controller层VO封装ResultVO 前后台交互的统一格式模板ProductVO :返回给前台的商品信息格式,包含目录信息ProductInfoVO 具体产品的数据VOController层逻辑启动测试知识点总结Java8的StreamBeanutils.copyProperties( )Github地址

Spring Cloud【Finchley】专栏

如果还没有系统的学过Spring Cloud ,先到我的专栏去逛逛吧

Spring Cloud 【Finchley】手札

概述

点餐系统,重点体会使用Spring Cloud微服务组件如何拆分系统

优秀的系统都是演进而来的,不要害怕出错,大胆折腾吧。

我们先来针对商品微服务进行设计和构建

版本说明

spring boot : 2.0.3.RELEASEspring 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="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /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 namespring: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/eureka

Step3. 启动类增加@EnableEurekaServer

package com.artisan.eurekaserver;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.flix.eureka.server.EnableEurekaServer;@SpringBootApplication@EnableEurekaServerpublic class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}}

Step4 启动测试

启动application,访问 http://localhost:8761/

Github地址

/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', '-03-20 22:47:41', '-03-20 22:47:41');INSERT INTO `product_category` VALUES ('2', '酒水', '98', '-03-20 22:48:13', '-03-20 22:48:13');INSERT INTO `product_category` VALUES ('3', '甜品', '97', '-03-20 22:47:51', '-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', '-03-20 22:49:47', '-03-20 22:49:50');INSERT INTO `product` VALUES ('2', '青岛纯生', '200', '7.50', '啤酒', null, '0', '98', '-03-20 22:50:48', '-03-20 22:50:55');INSERT INTO `product` VALUES ('3', '卡布奇诺', '87', '15.00', '卡布奇诺的香味', null, '0', '99', '-03-20 22:51:53', '-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.flix.eureka.EnableEurekaClient;@SpringBootApplication@EnableEurekaClientpublic 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插件

<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>

配置文件增加数据库配置

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# Eurekaeureka: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表示这个类是一个实体类@Entitypublic 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注解

@Componentpublic 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)@SpringBootTestpublic 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")@Entitypublic 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 flix.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)@SpringBootTestpublic 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;@Servicepublic 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;@Getterpublic 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.ponent;import java.util.List;import static org.junit.Assert.*;@Componentpublic 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;@Servicepublic 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.ponent;import java.util.Arrays;import java.util.List;import static org.junit.Assert.*;@Componentpublic 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;@Getterpublic 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;@Getterpublic 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;@Datapublic 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;@Datapublic 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串即可

package com.artisan.product.controller;import com.artisan.product.domain.Product;import com.artisan.product.domain.ProductCategory;import com.artisan.product.service.ProductCategoryService;import com.artisan.product.service.ProductService;import com.artisan.product.vo.ProductInfoVO;import com.artisan.product.vo.ProductVO;import com.artisan.product.vo.Result;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;@RestController@RequestMapping("/product")public class ProductController {@Autowiredprivate ProductService productService;@Autowiredprivate ProductCategoryService categoryService;@GetMapping("/list")private Result list() {//1. 查询所有在架的商品List<Product> productInfoList = productService.getAllUpProduct();//2. 获取类目type列表List<Integer> categoryTypeList = productInfoList.stream().map(Product::getCategoryType).collect(Collectors.toList());//3. 从数据库查询类目List<ProductCategory> categoryList = categoryService.findByCategoryTypeIn(categoryTypeList);//4. 构造数据List<ProductVO> productVOList = new ArrayList<>();for (ProductCategory productCategory : categoryList) {ProductVO productVO = new ProductVO();// 设置属性productVO.setCategoryName(productCategory.getCategoryName());productVO.setCategoryType(productCategory.getCategoryType());// ProductInfoVO 集合List<ProductInfoVO> productInfoVOList = new ArrayList<>();for (Product product : productInfoList) {// 挂到对应的的categoryType下if (product.getCategoryType().equals(productCategory.getCategoryType())) {ProductInfoVO productInfoVO = new ProductInfoVO();// 将属性copy到productInfoVO,避免逐个属性set,更简洁BeanUtils.copyProperties(product, productInfoVO);productInfoVOList.add(productInfoVO);}}productVO.setProductInfoVOList(productInfoVOList);productVOList.add(productVO);}return Result.success(productVOList);}}

启动测试

访问 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对象中。

同时mons.beanutils.BeanUtils也有个copyProperties

需要注意的是这俩的copyProperties方法参数位置不同

org.springframework.beans.BeanUtils#copyProperties(sourceDemo, targetDemo)mons.beanutils.BeanUtils#copyProperties(targetDemo, sourceDemo)

Github地址

/yangshangwei/springcloud-o2o/tree/master/artisan-product

如果觉得《Spring Cloud【Finchley】实战-01注册中心及商品微服务》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。