SpringCloud 笔记
简单微服务架构
微服务架构的基础框架/组件
- 服务注册发现
- 服务网关(Service Gateway)
- 后端通用服务(也称中间层服务Middle Tier Service)
- 前端服务(也称边缘服务Edge Service)
SpringCloud架构
- 所有请求都统一通过 API 网关(Zuul)来访问内部服务。
- 网关接收到请求后,从注册中心(Eureka)获取可用服务。
- 由 Ribbon 进行均衡负载后,分发到后端的具体实例。
- 微服务之间通过 Feign 进行通信处理业务。
- Hystrix 负责处理服务超时熔断。
- Turbine 监控服务间的调用和熔断相关指标。
两大”配方“
阿里系
- Dubbo
- zookeeper
- springmvc or springboot
- ...
SpringCloud
- spring cloud netflix eureka
- springboot
- ...
SpringCloud 是什么
SpringCloud 是一个开发工具集,包含多个子项目
- 利用springboot 的开发便利
- 主要是基于对Netflix 开源组件的进一步封装
SpringCloud 简化了分布式开发
- 分布式/版本化配置。
- 服务注册和发现。
- 路由。
- 服务和服务之间的调用。
- 负载均衡。
- 断路器。
- 分布式消息传递。
掌握如何使用,更要理解分布式、架构的特点
Spring Cloud 工具框架
- Spring Cloud Config,配置中心,利用 git 集中管理程序的配置。
- Spring Cloud Netflix,集成众多 Netflix 的开源软件。
- Spring Cloud Bus,消息总线,利用分布式消息将服务和服务实例连接在一起,用于在一个集群中传播状态的变化 。
- Spring Cloud for Cloud Foundry,利用 Pivotal Cloudfoundry 集成你的应用程序。
- Spring Cloud Foundry Service Broker,为建立管理云托管服务的服务代理提供了一个起点。
- Spring Cloud Cluster,基于 Zookeeper、Redis、Hazelcast、Consul 实现的领导选举和平民状态模式的抽象和实现。
- Spring Cloud Consul,基于 Hashicorp Consul 实现的服务发现和配置管理。
- Spring Cloud Security,在 Zuul 代理中为 OAuth2 rest 客户端和认证头转发提供负载均衡。
- Spring Cloud Sleuth Spring Cloud,应用的分布式追踪系统和 Zipkin、HTrace、ELK 兼容。
- Spring Cloud Data Flow,一个云本地程序和操作模型,组成数据微服务在一个结构化的平台上。
- Spring Cloud Stream,基于 Redis、Rabbit、Kafka 实现的消息微服务,简单声明模型用以在 Spring Cloud 应用中收发消息。
- Spring Cloud Stream App Starters,基于 Spring Boot 为外部系统提供 Spring 的集成。
- Spring Cloud Task,短生命周期的微服务,为 Spring Boot 应用简单声明添加功能和非功能特性。
- Spring Cloud Task App Starters。
- Spring Cloud Zookeeper,服务发现和配置管理基于 Apache Zookeeper。
- Spring Cloud for Amazon Web Services,快速和亚马逊网络服务集成。
- Spring Cloud Connectors,便于 PaaS 应用在各种平台上连接到后端像数据库和消息经纪服务。
- Spring Cloud Starters,项目已经终止并且在 Angel.SR2 后的版本和其他项目合并。
- Spring Cloud CLI,插件用 Groovy 快速的创建 Spring Cloud 组件应用。
Spring Cloud Eureka
基于Netflix Eureka 做了二次封装
两个组件组成
Eureka Server 注册中心
一、创建
二、打包并运行
- 项目更目录下面运行打包命令 mvn clean package
- java -jar xxxx.jar 启动(后台运行 nohup java -jar xxxxx.jar > /dev/null 2>&1 &)
Eureka Client 服务注册
一、创建
统一版本
高可用
spring-cloud为基础的微服务架构,所有的微服务都需要注册到注册中心,如果这个注册中心阻塞或者崩了,那么整个系统都无法继续正常提供服务,所以,这里就需要对注册中心进行集群,换言之,高可用(HA)
服务发现的两种方式
- 客户端发现
- Eureka
- 服务端发现
- Nginx
- Dubbo、Zookeeper
- Kubernetes
微服务中的服务拆分
服务拆分的方法论
如何拆“功能”
- 单一职责,松耦合、高内聚
- 关注点分离
- 按职责
- 按通用性
- 按粒度级别
服务和数据的关系
- 先考虑业务功能,再考虑数据
- 无状态服务
商品服务API和SQL介绍
API
商品列表
GET /product/list
参数
无
返回
{
"code": 0,
"msg": "成功",
"data": [
{
"name": "热榜",
"type": 1,
"foods": [
{
"id": "123456",
"name": "皮蛋粥",
"price": 1.2,
"description": "好吃的皮蛋粥",
"icon": "http://xxx.com",
}
]
},
{
"name": "好吃的",
"type": 2,
"foods": [
{
"id": "123457",
"name": "慕斯蛋糕",
"price": 10.9,
"description": "美味爽口",
"icon": "http://xxx.com",
}
]
}
]
}
创建订单
POST /order/create
参数
name: "张三"
phone: "18868822111"
address: "慕课网总部"
openid: "ew3euwhd7sjw9diwkq" //用户的微信openid
items: [{
productId: "1423113435324",
productQuantity: 2 //购买数量
}]
返回
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "147283992738221"
}
}
买家登录
GET /login/buyer
参数
openid: abc
返回
cookie里设置openid=abc
{
code: 0,
msg: "成功",
data: null
}
卖家登录
GET /login/seller
参数
openid: xyz
返回
cookie里设置token=UUID, redis设置key=UUID, value=xyz
{
code: 0,
msg: "成功",
data: null
}
SQL
-- 类目
create table `product_category` (
`category_id` int not null auto_increment,
`category_name` varchar(64) not null comment '类目名字',
`category_type` int not null 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 (`category_id`),
unique key `uqe_category_type` (`category_type`)
);
INSERT INTO `product_category` (`category_id`, `category_name`, `category_type`, `create_time`, `update_time`)
VALUES
(1,'热榜',11,'2017-03-28 16:40:22','2017-11-26 23:39:36'),
(2,'好吃的',22,'2017-03-14 17:38:46','2017-11-26 23:39:40');
-- 商品
create table `product_info` (
`product_id` varchar(32) not null,
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '单价',
`product_stock` int not null comment '库存',
`product_description` varchar(64) comment '描述',
`product_icon` varchar(512) comment '小图',
`product_status` tinyint(3) DEFAULT '0' COMMENT '商品状态,0正常1下架',
`category_type` int not null 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 (`product_id`)
);
INSERT INTO `product_info` (`product_id`, `product_name`, `product_price`, `product_stock`, `product_description`, `product_icon`, `product_status`, `category_type`, `create_time`, `update_time`)
VALUES
('157875196366160022','皮蛋粥',0.01,39,'好吃的皮蛋粥','//fuss10.elemecdn.com/0/49/65d10ef215d3c770ebb2b5ea962a7jpeg.jpeg',0,1,'2017-03-28 19:39:15','2017-07-02 11:45:44'),
('157875227953464068','慕斯蛋糕',10.90,200,'美味爽口','//fuss10.elemecdn.com/9/93/91994e8456818dfe7b0bd95f10a50jpeg.jpeg',1,1,'2017-03-28 19:35:54','2017-04-21 10:05:57'),
('164103465734242707','蜜汁鸡翅',0.02,982,'好吃','//fuss10.elemecdn.com/7/4a/f307f56216b03f067155aec8b124ejpeg.jpeg',0,1,'2017-03-30 17:11:56','2017-06-24 19:20:54');
-- 订单
create table `order_master` (
`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`)
);
-- 订单商品
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`)
);
-- 用户
CREATE TABLE `user_info` (
`id` varchar(32) NOT NULL,
`username` varchar(32) DEFAULT '',
`password` varchar(32) DEFAULT '',
`openid` varchar(64) DEFAULT '' COMMENT '微信openid',
`role` tinyint(1) NOT NULL COMMENT '1买家2卖家',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`)
);
商品服务编码实战
一、创建
二、项目初始化pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
三、启动类添加该注解
四、基础配置
五、添加数据库依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
添加配置
spring:
application:
name: product
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://192.168.180.199:3306/SpringCloud_Sell?characterEncoding=utf-8&useSSL=false
jpa:
show-sql: true
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
六、添加lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
七、测试
- 商品查询测试
编写实体类
package com.springcloud.product.dataobject;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Date;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-27
* @Time: 22:36
* @ProjectName: product
* @Name: ProductInfo
* @Version: 1.0.0
* @Description: 商品实体类
*/
//@Table(name = "T_xxxxxxx")
@Data
@Entity
public class ProductInfo {
@Id
private String productId;
private String productName;
private BigDecimal productPrice;
private Integer productStock;
private String productDescription;
private String productIcon;
private Integer productStatus;
private Integer categoryType;
private Date createTime;
private Date updateTime;
}
编写商品repository接口
package com.springcloud.product.repository;
import com.springcloud.product.dataobject.ProductInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-27
* @Time: 22:36
* @ProjectName: product
* @Name: ProductInfoRepository
* @Version: 1.0.0
* @Description: 商品repository接口
*/
public interface ProductInfoRepository extends JpaRepository<ProductInfo,String> {
/**
* 通过状态查询商品
* @param productStatus
* @return
*/
List<ProductInfo> findByProductStatus(Integer productStatus);
}
编写测试类
package com.springcloud.product.repository;
import com.springcloud.product.dataobject.ProductInfo;
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;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductInfoRepositoryTest {
@Autowired
private ProductInfoRepository productInfoRepository;
@Test
public void findByProductStatus() throws Exception{
List<ProductInfo> productInfoList = productInfoRepository.findByProductStatus(1);
Assert.assertTrue(productInfoList.size() > 0);
}
}
- 商品类目查询测试
编写实体类
package com.springcloud.product.dataobject;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-27
* @Time: 23:02
* @ProjectName: product
* @Name: ProductCategory
* @Version: 1.0.0
* @Description: 商品类别
*/
@Data
@Entity
public class ProductCategory {
@Id
@GeneratedValue
private Integer categoryId;
private String categoryName;
private Integer categoryType;
private Date createTime;
private Date updateTime;
}
编写DAO层代码
package com.springcloud.product.repository;
import com.springcloud.product.dataobject.ProductCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-27
* @Time: 22:36
* @ProjectName: product
* @Name: ProductCategoryRepository
* @Version: 1.0.0
* @Description: 商品类目repository接口
*/
public interface ProductCategoryRepository extends JpaRepository<ProductCategory,Integer> {
/**
* 通过类目状态查询商品类目
* @param categoryTypeList
* @return
*/
List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
}
编写测试类
package com.springcloud.product.repository;
import com.springcloud.product.dataobject.ProductCategory;
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.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductCategoryRepositoryTest {
@Autowired
private ProductCategoryRepository productCategoryRepository;
@Test
public void findByCategoryTypeTest() throws Exception{
List<ProductCategory> productCategoryList = productCategoryRepository.findByCategoryTypeIn(Arrays.asList(11,12));
Assert.assertTrue(productCategoryList.size() > 0);
}
}
八、编写service
- interface
- ProductService
package com.springcloud.product.service;
import com.springcloud.product.dataobject.ProductInfo;
import java.util.List;
/**
* @author journey
*/
public interface ProductService {
List<ProductInfo> findUpAll();
}
- ProductCategoryService
package com.springcloud.product.service;
import com.springcloud.product.dataobject.ProductCategory;
import java.util.List;
/**
* @author journey
*/
public interface ProductCategoryService {
/**
* 通过categoryType 查找productCategory
* @param categoryTypeList
* @return
*/
List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
}
- 接口实现类
package com.springcloud.product.service.impl;
import com.springcloud.product.common.ProductStatusEnum;
import com.springcloud.product.dataobject.ProductInfo;
import com.springcloud.product.repository.ProductInfoRepository;
import com.springcloud.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-28
* @Time: 00:02
* @ProjectName: product
* @Name: ProductServiceImpl
* @Version: 1.0.0
* @Description: 商品service
*/
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductInfoRepository productInfoRepository;
@Override
public List<ProductInfo> findUpAll() {
return productInfoRepository.findByProductStatus(ProductStatusEnum.UP.getCode());
}
}
package com.springcloud.product.service.impl;
import com.netflix.discovery.converters.Auto;
import com.springcloud.product.dataobject.ProductCategory;
import com.springcloud.product.repository.ProductCategoryRepository;
import com.springcloud.product.service.ProductCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 11:20
* @ProjectName: product
* @Name: ProductCategoryServiceImpl
* @Version: 1.0.0
* @Description: 商品类目接口实现类
*/
@Service
public class ProductCategoryServiceImpl implements ProductCategoryService {
@Autowired
private ProductCategoryRepository productCategoryRepository;
@Override
public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) {
return productCategoryRepository.findByCategoryTypeIn(categoryTypeList);
}
}
- 编写返回数据结构类
{
"code": 0,
"msg": "成功",
"data": [
{
"name": "热榜",
"type": 1,
"foods": [
{
"id": "123456",
"name": "皮蛋粥",
"price": 1.2,
"description": "好吃的皮蛋粥",
"icon": "http://xxx.com",
}
]
},
{
"name": "好吃的",
"type": 2,
"foods": [
{
"id": "123457",
"name": "慕斯蛋糕",
"price": 10.9,
"description": "美味爽口",
"icon": "http://xxx.com",
}
]
}
]
}
ResultVO
package com.springcloud.product.vo;
import lombok.Data;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 11:32
* @ProjectName: product
* @Name: ResultVO
* @Version: 1.0.0
* @Description: http请求返回的最外层对象
*/
@Data
public class ResultVO<T> {
/**
* 状态码
*/
private Integer code;
/**
* 信息
*/
private String msg;
/**
* 具体内容
*/
private T data;
}
ProductVO
package com.springcloud.product.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 11:37
* @ProjectName: product
* @Name: ProductVO
* @Version: 1.0.0
* @Description: ProductVO
*/
@Data
public class ProductVO {
@JsonProperty("name")
private String categoryName;
@JsonProperty("type")
private Integer categoryType;
@JsonProperty("foods")
List<ProductInfoVO> productInfoVOList;
}
ProductInfoVO
package com.springcloud.product.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 11:40
* @ProjectName: product
* @Name: ProductInfoVO
* @Version: 1.0.0
* @Description: ProductInfoVO
*/
@Data
public class ProductInfoVO {
@JsonProperty("id")
private Integer productId;
@JsonProperty("name")
private String productName;
@JsonProperty("price")
private BigDecimal productPrice;
@JsonProperty("description")
private String productDescription;
@JsonProperty("icon")
private String productIcon;
}
编写ResultVOUtil
package com.springcloud.product.util;
import com.springcloud.product.vo.ResultVO;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 12:32
* @ProjectName: product
* @Name: ResultVOUtil
* @Version: 1.0.0
* @Description: ResultVOUtil
*/
public class ResultVOUtil {
public static ResultVO success(Object object){
ResultVO resultVO = new ResultVO();
resultVO.setData(object);
resultVO.setMsg("成功");
resultVO.setCode(0);
return resultVO;
}
}
编写controller
ProductContoller
package com.springcloud.product.controller;
import com.springcloud.product.dataobject.ProductCategory;
import com.springcloud.product.dataobject.ProductInfo;
import com.springcloud.product.service.ProductCategoryService;
import com.springcloud.product.service.ProductService;
import com.springcloud.product.vo.ProductInfoVO;
import com.springcloud.product.vo.ProductVO;
import com.springcloud.product.vo.ResultVO;
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;
/**
* @author journey
*/
@RestController
@RequestMapping("/product")
public class ProductContoller {
@Autowired
private ProductService productService;
@Autowired
private ProductCategoryService productCategoryService;
/**
* 1. 查询所有在架的商品
* 2. 查询类目type列表
* 3. 查询类目
* 4. 构造数据
*/
@GetMapping("/list")
public ResultVO<ProductVO> list(){
List<ProductInfo> productInfoList = productService.findUpAll();
List<Integer> productCategoryTypeList = productInfoList.stream().map(ProductInfo::getCategoryType).collect(Collectors.toList());
List<ProductCategory> productCategoryList = productCategoryService.findByCategoryTypeIn(productCategoryTypeList);
List<ProductVO> productVOList = new ArrayList<>();
for(ProductCategory productCategory: productCategoryList){
ProductVO productVO = new ProductVO();
productVO.setCategoryName(productCategory.getCategoryName());
productVO.setCategoryType(productCategory.getCategoryType());
List<ProductInfoVO> productInfoVOList = new ArrayList<>();
for(ProductInfo productInfo: productInfoList){
if(productInfo.getCategoryType().equals(productCategory.getCategoryType())){
ProductInfoVO productInfoVO = new ProductInfoVO();
BeanUtils.copyProperties(productInfo , productInfoVO);
productInfoVOList.add(productInfoVO);
}
}
productVO.setProductInfoVOList(productInfoVOList);
productVOList.add(productVO);
}
return ResultVOUtil.success(productVOList);
}
}
订单服务
一、创建
二、编写相关代码
entity
OrderMaster
package com.springcloud.order.dataobject;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 13:44
* @ProjectName: order
* @Name: OrderMaster
* @Version: 1.0.0
* @Description: 订单实体
*/
@Data
@Entity
public class OrderMaster {
@Id
private String orderId;
private String buyerName;
private String buyerPhone;
private String buyerAddress;
private String buyerOpenid;
private BigDecimal orderAmount;
private Integer orderStatus;
private Integer payStatus;
private Date createTime;
private Date updateTime;
}
OrderDetail
package com.springcloud.order.dataobject;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 13:57
* @ProjectName: order
* @Name: OrderDetail
* @Version: 1.0.0
* @Description: OrderDetail
*/
@Data
@Entity
public class OrderDetail {
@Id
private String detailId;
private String orderId;
private String productId;
private String productName;
private BigDecimal productPrice;
private Integer productQuantity;
private String productIcon;
private Date createTime;
private Date updateTime;
}
reponsitory
- OrderMasterReponsitory
public interface OrderMasterReponsitory extends JpaRepository<OrderMaster, String> {
}
- OrderDetailReponsitory
public interface OrderDetailReponsitory extends JpaRepository<OrderDetail, String> {
}
enums
- OrderStatusEnum
package com.springcloud.order.common;
import lombok.Getter;
@Getter
public enum OrderStatusEnum {
NEW(0, "新订单"),
FINISHED(1, "完结"),
CANCEL(2, "取消"),
;
private Integer code;
private String message;
OrderStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
- PayStatusEnum
package com.springcloud.order.common;
import lombok.Getter;
@Getter
public enum PayStatusEnum {
WAIT(0, "等待支付"),
SUCCESS(1, "支付成功"),
;
private Integer code;
private String message;
PayStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
- ResultEnum
package com.springcloud.order.common;
import lombok.Getter;
@Getter
public enum ResultEnum {
PARAM_ERROR(1, "参数错误"),
CART_EMPTY(2, "购物车为空")
;
private Integer code;
private String message;
ResultEnum(Integer code, String message ){
this.code = code;
this.message = message;
}
}
controller
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public ResultVO<Map<String, String>> create(@Valid OrderForm orderForm,
BindingResult bindingResult) {
if(bindingResult.hasErrors()){
log.error("【创建订单】参数不正确, orderForm={}", orderForm);
throw new OrderException(ResultEnum.PARAM_ERROR.getCode(),
bindingResult.getFieldError().getDefaultMessage());
}
OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
log.error("【创建订单】购物车信息为空");
throw new OrderException(ResultEnum.CART_EMPTY);
}
OrderDTO result = orderService.create(orderDTO);
Map<String, String> map = new HashMap<>();
map.put("orderId", result.getOrderId());
return ResultVOUtil.success(map);
}
}
vo
- ResultVO
@Data
public class ResultVO<T> {
private Integer code;
private String msg;
private T data;
}
form
- OrderForm
@Data
public class OrderForm {
@NotEmpty(message = "姓名必填")
private String name;
@NotEmpty(message = "手机号必填")
private String phone;
@NotEmpty(message = "地址必填")
private String address;
@NotEmpty(message = "openid必填")
private String openid;
@NotEmpty(message = "购物车不能为空")
private String items;
}
exception
- OrderException
public class OrderException extends RuntimeException{
private Integer code;
public OrderException(Integer code, String message) {
super(message);
this.code = code;
}
public OrderException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}
converter
-OrderForm2OrderDTOConverter
@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 {
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;
}
}
List<OrderDetail> orderDetailList = new ArrayList<>();
try {
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);
dto
- OrderDTO
package com.springcloud.order.dto;
import com.springcloud.order.dataobject.OrderDetail;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 14:42
* @ProjectName: order
* @Name: OrderDTO
* @Version: 1.0.0
* @Description: OrderDTO
*/
@Data
public class OrderDTO {
/** 订单id. */
private 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 List<OrderDetail> orderDetailList;
}
注意:使用json转化
name: "张三"
phone: "18868822111"
address: "慕课网总部"
openid: "ew3euwhd7sjw9diwkq" //用户的微信openid
items: [{
productId: "1423113435324",
productQuantity: 2 //购买数量
}]
service
- OrderService
/**
* @author journey
*/
public interface OrderService {
/**
* 创建订单
* @param orderDTO
* @return
*/
OrderDTO create(OrderDTO orderDTO);
}
- impl
- OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMasterReponsitory orderMasterReponsitory;
@Autowired
private OrderDetailReponsitory orderDetailReponsitory;
@Override
public OrderDTO create(OrderDTO orderDTO) {
String orderId = KeyUtil.genUniqueKey();
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderId(orderId);
BeanUtils.copyProperties(orderDTO, orderMaster);
orderMaster.setOrderAmount(new BigDecimal(5));
orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
orderMasterReponsitory.save(orderMaster);
return orderDTO;
}
}
util
- ResultVOUtil
public class ResultVOUtil {
public static ResultVO success(Object object) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(0);
resultVO.setMsg("成功");
resultVO.setData(object);
return resultVO;
}
}
- KeyUtil
public class KeyUtil {
/**
* 生成唯一的主键
* 格式: 时间+随机数
*/
public static synchronized String genUniqueKey() {
Random random = new Random();
Integer number = random.nextInt(900000) + 100000;
return System.currentTimeMillis() + String.valueOf(number);
}
}
应用间的通信
HTTP
SpringCloud 中服务间两种restful调用方式
RestTemplate
第一种方式:直接使用restTemplate,url写死
@Slf4j
@RestController
public class ClientController {
@GetMapping("/getProductMsg")
public String getProductMsg(){
// 1、第一种方式(直接使用restTemplate,url写死)
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://localhost:9082/msg",String.class);
log.info("response={}",response);
return response;
}
}
第二种方式:利用loadBalancerClient通过应用名获取url,然后再使用restTemplate
@Slf4j
@RestController
public class ClientController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/getProductMsg")
public String getProductMsg(){
//2、第二种方式(利用loadBalancerClient通过应用名获取url,然后再使用restTemplate)
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()) + "/msg";
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url,String.class);
log.info("response={}",response);
return response;
}
}
第三种方式:利用@LoadBalanced,可再restTemplate里使用应用名字
@Component
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@Slf4j
@RestController
public class ClientController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getProductMsg")
public String getProductMsg(){
//3、第三种方式(利用@LoadBalanced,可再restTemplate里使用应用名字)
String response = restTemplate.getForObject("http://PRODUCT/msg",String.class);
log.info("response={}",response);
return response;
}
}
Feign
Feign负载均衡
- Feign 解决什么问题
- Feign设计原理
使用Feign实现组件间的通信
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类上加注解
@EnableFeignClients
编写调用接口
@FeignClient(name = "product")
public interface ProductClient {
@GetMapping("/msg")
String productMsg();
}
编写controller
@RestController
@Slf4j
public class FeginProductController {
@Autowired
private ProductClient productClient;
@GetMapping("/getProductMsg")
public String getProductMsg(){
String response = productClient.productMsg();
log.info("response={}" ,response);
return response;
}
}
fegin实例
获取商品列表
product
- controller
/**
* 获取商品列表(给订单服务用的)
*
* @param productIdList
* @return
*/
@PostMapping("/listForOrder")
public List<ProductInfo> listForOrder(@RequestBody List<String> productIdList) {
return productService.findList(productIdList);
}
- service
List<ProductInfo> findList(List<String> productIdList);
- impl
@Override
public List<ProductInfo> findList(List<String> productIdList) {
return productInfoRepository.findByProductIdIn(productIdList);
}
- reponsitory
List<ProductInfo> findByProductIdIn(List<String> productIdList);
order
- client
@FeignClient(name = "product")
public interface ProductClient {
@GetMapping("/msg")
String productMsg();
@PostMapping("/product/listForOrder")
List<ProductInfo> listForOrder(@RequestBody List<String> productIdList);
}
- controller
@PostMapping("/listForOrder")
public List<ProductInfo> listForOrder(@RequestBody List<String> productIdList) {
return productService.findList(productIdList);
}
扣库存
product
- controller
/**
* 根据商品id 和数量扣去相应存储数量
*
* @param cartDTOList
*/
@PostMapping("/decreaseStock")
public void decreaseStocl(@RequestBody List<CartDTO> cartDTOList){
productService.decreaseStock(cartDTOList);
}
- service
void decreaseStock(List<CartDTO> cartDTOList);
- impl
@Override
@Transactional
public void decreaseStock(List<CartDTO> cartDTOList) {
for(CartDTO cartDTO: cartDTOList){
Optional<ProductInfo> productInfoOptional = productInfoRepository.findById(cartDTO.getProductId());
// 判断商品是否存在
if(!productInfoOptional.isPresent()){
throw new ProductException(ResultEnum.PRODUCT_NOT_EXIST);
}
// 库存是否足够
ProductInfo productInfo = productInfoOptional.get();
Integer result = productInfo.getProductStock() - cartDTO.getProductQuantity();
if(result < 0){
throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
}
productInfo.setProductStock(result);
productInfoRepository.save(productInfo);
}
}
Optional:java8新特性类,主要解决空指针异常(NullPointerException)问题。
https://www.runoob.com/java/java8-optional-class.html
dto
- CartDTO
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-07-07
* @Time: 15:22
* @ProjectName: product
* @Name: CartDTO
* @Version: 1.0.0
* @Description: CartDTO
*/
@Data
public class CartDTO {
/**
* 商品id
*/
private String productId;
/**
* 商品数量
*/
private Integer productQuantity;
public CartDTO() {
}
public CartDTO(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
}
exception
- ProductException
public class ProductException extends RuntimeException {
private Integer code;
public ProductException(Integer code , String message){
super(message);
this.code = code;
}
public ProductException(ResultEnum resultEnum){
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}
enums
- ResultEnum
@Getter
public enum ResultEnum {
PRODUCT_NOT_EXIST(1, "商品不存在"),
PRODUCT_STOCK_ERROR(2, "库存有误")
;
private Integer code;
private String message;
ResultEnum(Integer code , String message){
this.code = code;
this.message = message;
}
}
order
- client
@PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List<CartDTO> cartDTOList);
- FeginProductController
@GetMapping("/productDecreaseStock")
public String productDecreaseStock(){
productClient.decreaseStock(Arrays.asList(new CartDTO("157875196366160022",2)));
return "ok";
}
整合接口打通下单流程
- OrderServiceImpl
/**
* Create with IntelliJ IDEA.
*
* @Author: journey
* @Date: 2019-06-30
* @Time: 14:45
* @ProjectName: order
* @Name: OrderServiceImpl
* @Version: 1.0.0
* @Description: OrderServiveImpl
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMasterReponsitory orderMasterReponsitory;
@Autowired
private OrderDetailReponsitory orderDetailReponsitory;
@Autowired
private ProductClient productClient;
@Override
public OrderDTO create(OrderDTO orderDTO) {
String orderId = KeyUtil.genUniqueKey();
// 查询商品信息
List<String> productIdList = orderDTO.getOrderDetailList().stream().map(OrderDetail::getProductId).collect(Collectors.toList());
List<ProductInfo> productInfoList = productClient.listForOrder(productIdList);
// 计算总价
BigDecimal bigDecimal = new BigDecimal(0);
for(OrderDetail orderDetail: orderDTO.getOrderDetailList()){
for(ProductInfo productInfo: productInfoList){
if(productInfo.getProductId().equals(orderDetail.getProductId())){
// 单价 * 数量
bigDecimal = productInfo.getProductPrice().multiply(new BigDecimal(orderDetail.getProductQuantity())).add(bigDecimal);
BeanUtils.copyProperties(productInfo,orderDetail);
orderDetail.setOrderId(orderId);
orderDetail.setDetailId(KeyUtil.genUniqueKey());
// 订单详情入库
orderDetailReponsitory.save(orderDetail);
}
}
}
// 扣库存
List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream().map(e -> new CartDTO(e.getProductId(), e.getProductQuantity())).collect(Collectors.toList());
productClient.decreaseStock(cartDTOList);
// 订单入口
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderId(orderId);
BeanUtils.copyProperties(orderDTO, orderMaster);
// orderMaster.setOrderAmount(new BigDecimal(5));
orderMaster.setOrderAmount(bigDecimal);
orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
orderMasterReponsitory.save(orderMaster);
return orderDTO;
}
}
微服务之间的服务调用
服务调用示例
项目改成多模块
外层product pom
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springcloud</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>product</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>product-common</module>
<module>product-client</module>
<module>product-server</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
product-common pom
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.springcloud</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
product-client初始pom.xml
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.springcloud</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-client</artifactId>
<dependencies>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
product-server的初始pom.xml
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springcloud</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>product</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>product-common</module>
<module>product-client</module>
<module>product-server</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
为product-server添加Eureka client相关依赖
<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>
启动类加注解@EnableDiscoveryClient
多模块项目创建
构建主工程
File-->New-->Project
-->Maven-->Create from archetype-->maven-archetype-quickstart-Next
-->GroupId={你的GroupId}-->AritifactId={你的ArtifactId}
-->Next-->Next-->Finish-->New Whindow
构建子模块
右键点击项目名称-->New-->Module
选中Spring Initializr-->Next
-->Group={主工程的GroupId}-->Aritifact={当前模块的ArtifactId}、
-->Next-->Next-->Finish
product
root
product的pom
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springcloud</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>product-common</module>
<module>product-client</module>
<module>product-server</module>
</modules>
<packaging>pom</packaging>
<name>product</name>
<description>SpringCloud_sell's Demo project for Spring Cloud</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
modules
product-common的pom
<parent>
<artifactId>product</artifactId>
<groupId>com.springcloud</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.springcloud</groupId>
<artifactId>product-common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
product-client的pom
<parent>
<artifactId>product</artifactId>
<groupId>com.springcloud</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.springcloud</groupId>
<artifactId>product-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
product-server的pom
<parent>
<artifactId>product</artifactId>
<groupId>com.springcloud</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.springcloud</groupId>
<artifactId>product-server</artifactId>
<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>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-amqp</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-sleuth</artifactId>-->
<!--</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
将product 项目打包成可供依赖的jar包
order
root
order的pom
<modelVersion>4.0.0</modelVersion>
<modules>
<module>order-common</module>
<module>order-client</module>
<module>order-server</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springcloud</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>order</name>
<description>SpringCloud_sell's Demo project for Spring Cloud</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>order-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
modules
order-common的pom
<parent>
<artifactId>order</artifactId>
<groupId>com.springcloud</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.springcloud</groupId>
<artifactId>order-common</artifactId>
<name>order-common</name>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
order-client的pom
<parent>
<artifactId>order</artifactId>
<groupId>com.springcloud</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.springcloud</groupId>
<artifactId>order-client</artifactId>
<name>order-client</name>
order-server的pom
<parent>
<artifactId>order</artifactId>
<groupId>com.springcloud</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.springcloud</groupId>
<artifactId>order-server</artifactId>
<name>order-server</name>
<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>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>product-client</artifactId>
</dependency>
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>order-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-data-redis</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-sleuth</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-sleuth-zipkin</artifactId>-->
<!--</dependency>-->
<!--包含sleuth和zipkin-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-zipkin</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-bus-amqp</artifactId>-->
<!--</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
接口暴露与发现
product
@FeignClient(name="product",fallback = ProductClient.ProductClientFallback.class)
public interface ProductClient {
@PostMapping("/product/listForOrder")
List<ProductInfoOutput> listForOrder(@RequestBody List<String> productIdList);
@PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List<DecreaseStockInput> decreaseStockInputList);
@Component
static class ProductClientFallback implements ProductClient {
@Override
public List<ProductInfoOutput> listForOrder(List<String> productIdList) {
return null;
}
@Override
public void decreaseStock(List<DecreaseStockInput> decreaseStockInputList) {
}
}
}
order
@EnableFeignClients(basePackages = "com.product.xxxxxxxx")
统一配置中心 Spring Cloud Config
统一配置中心概述
如果微服务架构中没有使用统一配置中心时,所存在的问题:
- 配置文件分散在各个项目里,不方便维护
- 配置内容安全与权限,实际开发中,开发人员是不知道线上环境的配置的
- 更新配置后,项目需要重启
Config server
新建一个Spring Initializr项目
pom依赖
添加注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
新建git仓库
略
配置yml文件
spring:
application:
name: config
cloud:
config:
server:
git:
uri: https://github.com/journeyC/SpringCloud.git
username: 15251693879@163.com
password:
baseDir: /Users/journey/code/............
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
启动并运行
http://localhost:8080/order-a.yml
http://localhost:8080/order-a.properties
http://localhost:8080/order-a.json
config client
项目pom文件中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
启动类上添加注解
@EnableDiscoveryClient
配置yml文件
将文件名改为 bootstrap.yml
spring:
application:
name: product
cloud:
config:
discovery:
service-id: CONFIG
enabled: true
profile: dev
配置文件路径名称
/{name}-{profiles}.yml
/{label}/{name}-{profiles}.yml
- label 分支
- name 服务
- profiles 环境
- .yml/json/properties 配置文件格式
Spring Cloud Bus
新增pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
修改yml
rabbitmq:
host: 192.168.180.199
port: 5672
username: admin
password: admin
使用bus-refresh
management:
endpoints:
web:
exposure:
include: "*"
http://localhost:8080/actuator/bus-refresh (post)
集成WebHooks实现动态更新
Ribbon负载均衡
添加Ribbon依赖
<!-- Ribbon相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
修改yml
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://xxx:8761/eureka/,http://xxx:8762/eureka/,http://xxx:8763/eureka/
对ConfigBean进行新注解@LoadBalanced 获得Rest时加入Ribbon的配置
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
异步和消息
RabbitMQ的基本使用
接收MQ消息
@Slf4j
@Component
public class MqReceiver {
// 手动创建队列,并绑定@RabbitListener(queues = "myQueue")
// 自动创建队列 @RabbitListener(queuesToDeclare = @Queue("myQueue"))
// 自动创建 Exchange和Queue绑定
@RabbitListener(bindings = @QueueBinding(
value = @Queue("myQueue"),
exchange = @Exchange("myExchange")
))
public void process(String message) {
log.info("接受到rabbitMQ消息:{}", message);
}
/**
* 数码供应商服务 接受消息
* @param message 接受消息
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue("myOrder"),
key = "computer",
exchange = @Exchange("computerOrder")
))
public void processComputer(String message) {
log.info("Computer,接受到rabbitMQ消息:{}", message);
}
/**
* 水果供应商服务 接受消息
* @param message 接受消息
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue("myOrder"),
key = "fruit",
exchange = @Exchange("fruitOrder")
))
public void processFruit(String message) {
log.info("fruit,接受到rabbitMQ消息:{}", message);
}
}
手动创建队列,并绑定@RabbitListener(queues = "myQueue")
自动创建队列 @RabbitListener(queuesToDeclare = @Queue("myQueue"))
自动创建 Exchange和Queue绑定
MQReceiverTest
/**
* @Author: journey
* @Date: 2019-07-21
* @Time: 16:03
* @Description: 发送mq消息测试
*/
public class MQReceiverTest extends OrderApplicationTest {
@Autowired
private AmqpTemplate amqpTemplate;
@Test
public void send() {
amqpTemplate.convertAndSend("myQueue", "发送消息测试");
}
@Test
public void sendMessage() {
amqpTemplate.convertAndSend("computerOrder","computer", "发送消息测试");
}
}
OrderApplicationTest
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderApplicationTest {
@Test
public void main() {
}
}
SpringCloud Stream的基础使用
引入pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
配置 yml
stream:
bindings:
myMessage:
group: order
发送方
@RestController
public class SendMessageController {
@Autowired
private StreamClient streamClient;
@GetMapping("/sendMessage")
public void process (){
String message = "now " + new Date();
streamClient.output().send(MessageBuilder.withPayload(message).build());
}
}
接收方
接口
public interface StreamClient {
String INPUT = "input";
String OUTPUT = "output";
@Input(StreamClient.INPUT)
SubscribableChannel input();
@Output(StreamClient.OUTPUT)
MessageChannel output();
}
实现类
/**
* @Author: journey
* @Date: 2019-07-21
* @Time: 16:48
* @Description:
*/
@Component
@EnableBinding(StreamClient.class)
@Slf4j
public class StreamReceiver {
@StreamListener(StreamClient.INPUT)
@SendTo(StreamClient.OUTPUT)
public Object process(Object message){
log.info("StreamReceiver: {}" , message);
return message;
}
@StreamListener(StreamClient.OUTPUT)
public void processOutput(Object message){
log.info("StreamReceiver: {}" , message);
}
}
SpringCloud Stream RabbitMQ Redis案例
product
添加pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
添加yml配置
rabbitmq:
host: 192.168.180.199
port: 5672
username: admin
password: admin
virtual-host: /
service
@Autowired
private AmqpTemplate amqpTemplate;
......
@Override
public void decreaseStock(List<DecreaseStockInput> decreaseStockInputList) {
// 发送 mq 消息
List<ProductInfoOutput> productInfoOutputList = decreaseStockProcess(decreaseStockInputList).stream()
.map(e -> {
ProductInfoOutput output = new ProductInfoOutput();
BeanUtils.copyProperties(e, output);
return output;
})
.collect(Collectors.toList());
amqpTemplate.convertAndSend("decreaseStockQueue", JsonUtil.toJson(productInfoOutputList));
}
@Transactional
public List<ProductInfo> decreaseStockProcess(List<DecreaseStockInput> decreaseStockInputList) {
List<ProductInfo> productInfoList = new ArrayList<>();
for(DecreaseStockInput decreaseStockInput : decreaseStockInputList){
Optional<ProductInfo> productInfoOptional = productInfoRepository.findById(decreaseStockInput.getProductId());
// 商品是否存在
if(!productInfoOptional.isPresent())
throw new ProductException(ResultEnum.PRODUCT_NOT_EXIST);
ProductInfo productInfo = productInfoOptional.get();
int result = productInfo.getProductStock() - decreaseStockInput.getProductQuantity();
// 商品库存是否充足
if(result < 0)
throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
productInfo.setProductStock(result);
productInfoRepository.save(productInfo);
productInfoList.add(productInfo);
}
return productInfoList;
}
order
ProductInfoReceiver
@Component
@Slf4j
public class ProductInfoReceiver {
@RabbitListener(queuesToDeclare = @Queue("decreaseStockQueue"))
public void process(String message){
// message => productInfoOutput
List<ProductInfoOutput> productInfoOutputList = JsonUtil.fromJson(message,
new TypeReference<List<ProductInfoOutput>>() {});
log.info("从队列【{}】接收到消息:{}", "decreaseStockQueue", productInfoOutputList);
}
}
添加redis数据库缓存
新增redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
添加yml配置
redis:
host: 192.168.180.199
port: 6379
database: 0
redis设置
redis-server /etc/redis.conf
/**
* @Author: journey
* @Date: 2019-07-21
* @Time: 20:09
* @Description:
*/
@Component
@Slf4j
public class ProductInfoReceiver {
private static String PRODUCT_STOCK_TEMPLATE = "product_stock_%s";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RabbitListener(queuesToDeclare = @Queue("decreaseStockQueue"))
public void process(String message){
// message => productInfoOutput
List<ProductInfoOutput> productInfoOutputList = JsonUtil.fromJson(message,
new TypeReference<List<ProductInfoOutput>>() {});
log.info("从队列【{}】接收到消息:{}", "decreaseStockQueue", productInfoOutputList);
// 存储到 redis 中
for(ProductInfoOutput productInfoOutput : productInfoOutputList)
stringRedisTemplate.opsForValue()
.set(String.format(PRODUCT_STOCK_TEMPLATE, productInfoOutput.getProductId()),
String.valueOf(productInfoOutput.getProductStock()));
}
}
服务网关和Zuul
zuul路由网关
添加pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--注册中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
添加yml配置
server:
port: 8000
spring:
application:
name: journey-zuul
zuul:
routes:
ribbon:
path: /ribbon/**
service-id: journey-edu #转发到消费者 /ribbon/
feign:
path: /feign/**
service-id: journey-edu # 转发到消费者 /feign/
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka
创建MyFallbackProvider文件
@Component
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "journey-edu";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("route:"+route);
System.out.println("exception:"+cause.getMessage());
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "ok";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("Sorry, the service is unavailable now.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
创建MyFilter
Component
public class MyFilter extends ZuulFilter {
private static Logger log=LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return "pre"; // 定义filter的类型,有pre、route、post、error四种
}
@Override
public int filterOrder() {
return 0; // 定义filter的顺序,数字越小表示顺序越高,越先执行
}
@Override
public boolean shouldFilter() {
return true; // 表示是否需要执行该filter,true表示执行,false表示不执行
}
@Override
public Object run() throws ZuulException {
// filter需要执行的具体操作
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
System.out.println(token);
if(token==null){
log.warn("there is no request token");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("there is no request token");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
log.info("ok");
return null;
}
}
创建启动类ZuulApplication
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
PRC
Dubbo