原文链接:https://mp.weixin.qq.com/s/BoKOEKzmJmHqVtgYBZyp-w
项目总体结构
项目总体上包含一个Maven父工程,实体类模块、工具类模块、用户微服务、商品微服务和订单微服务都以Maven子模块的形式存在,项目总体结果如下所示。

其中每个部分的含义如下所示。
其中各模块的说明如下所示:
- shop-Springcloud-alibaba:Maven父工程。
- shop-bean:各服务都会使用的JavaBean模块,包含实体类、Dto、Vo等JavaBean。
- shop-utils:各服务都会使用的工具类模块。
- shop-order:订单微服务,监听的端口为8080。
- shop-product:商品微服务,监听的端口为8070。
- shop-user:用户微服务,监听的端口为8060。
创建Maven父工程
这里,我使用的开发环境是大家都比较熟悉的IDEA,关于IDEA的使用,这里我就不再赘述了,如果有对IDEA的使用不太熟悉的小伙伴,那就自行百度或者谷歌吧,今天我们先重点撸源码。
在IDEA中创建Maven工程,名称为shop-springcloud-alibaba,创建后在项目的pom.xml文件中添加StringBoot与SpringCloud alibaba相关的配置,如下所示。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<properties>
<Java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
<spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
<logback.version>1.1.7</logback.version>
<slf4j.version>1.7.21</slf4j.version>
<common.logging>1.2</common.logging>
<fastjson.version>1.2.51</fastjson.version>
<mybatis.version>3.4.6</mybatis.version>
<mybatis.plus.version>3.4.1</mybatis.plus.version>
<mysql.jdbc.version>8.0.19</mysql.jdbc.version>
<druid.version>1.1.10</druid.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.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建工具类模块
在父工程下创建工具类模块shop-utils,作为整个项目的通用工具类模块。工具类模块的总体结构如下所示。

添加项目依赖
在shop-utils模块的pom.xml文件中添加项目依赖的一些类库,如下所示。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.jdbc.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${common.logging}</version>
</dependency>
<!-- log -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
核心类开发
1.创建HTTP状态码封装类
在项目的io.binghe.shop.utils.constants包下创建HttpCode类,作为HTTP状态码的常量类。这里,暂时定义了两个状态码,200表示处理成功,500表示服务器异常,源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description http状态码
*/
public class HttpCode {
/**
* 成功的状态码
*/
public static final int SUCCESS = 200;
/**
* 错误状态码
*/
public static final int FAILURE = 500;
}
2.创建全局异常捕获类
在项目的io.binghe.shop.utils.exception包下新建全局异常捕获类RestCtrlExceptionHandler,统一捕获整个项目抛出的Exception异常,源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 全局异常处理器
*/
@RestControllerAdvice
public class RestCtrlExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(RestCtrlExceptionHandler.class);
/**
* 全局异常处理,统一返回状态码
*/
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
logger.error("服务器抛出了异常:{}", e);
return new Result<String>(HttpCode.FAILURE, "执行失败", e.getMessage());
}
}
3.创建通用MD5与密码加密类
在io.binghe.shop.utils.md5包下新建MD5Hash类,提供通用的MD5加密算法,在io.binghe.shop.utils.psswd包下新建PasswordUtils类,提供密码的加密功能。这两个类的实现比较简单,这里就不再赘述了。感兴趣的小伙伴加入 【冰河技术】 知识星球获取源码。
4.创建通用数据响应类
在项目的io.binghe.shop.utils.resp包下新建Result类,用于封装统一的数据返回格式,源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 返回的结果数据
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1497405107265595284L;
/**
* 状态码
*/
private Integer code;
/**
* 状态描述
*/
private String codeMsg;
/**
* 返回的数据
*/
private T data;
}
这里,需要注意的是:在Result类中使用了泛型,返回的具体业务数据类型会根据泛型的具体类型确定。Result类中的每个字段的含义如下所示。
- code:返回的状态码。
- codeMsg:返回的状态描述信息。
- data:具体的业务数据,数据类型根据泛型确定。
5.创建分布式id核心类
(1)在项目的io.binghe.shop.utils.id包下创建实现整个分布式id最核心的类SnowFlake,SnowFlake类主要是使用Java实现了雪花算法,具体的逻辑见如下源码。
/**
* @author binghe
* @version 1.0.0
* @description 雪花算法生成分布式序列号
*/
public class SnowFlake {
/**
* 起始的时间戳:2022-04-12 11:56:45,使用时此值不可修改
*/
private final static long START_STAMP = 1649735805910L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATACENTER_BIT = 5;//数据中心占用的位数
/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastStmp = -1L;//上一次时间戳
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
/**
* 产生下一个ID
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStmp == lastStmp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastStmp = currStmp;
return (currStmp - START_STAMP) << TIMESTMP_LEFT //时间戳部分
| datacenterId << DATACENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private long getNewstmp() {
return System.currentTimeMillis();
}
public static Long getMaxDataCeneterNum() {
return MAX_DATACENTER_NUM;
}
public static Long getMaxMachineNum() {
return MAX_MACHINE_NUM;
}
}
根据雪花算法的实现可以发现,SnowFlake类提供了一个有参构造函数,如下所示。
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
其中,第一个参数datacenterId表示数据中心id,也可以认为是机房的id,machineId表示机器id,也可以认为是服务所在的服务器id。在SnowFlake的构造方法中,对这两个参数进行了限制,如下所示。
- datacenterId:大于或者等于0,并且要小于MAX_DATACENTER_NUM,也就是小于31。
- machineId:大于或者等于0,并且要小于MAX_DATACENTER_NUM,也就是小于31。
所以,类实现的雪花算法支持32个不同的数据中心或机房,并且在每个数据中心或机房中支持32个机器上部署分布式id服务。这对一般的场景来说,已经足够了。
注意:雪花算法的实现强依赖时间戳,所以在SnowFlake源码中存在如下常量,并标注了使用时此值不可更改的注释。
/**
* 起始的时间戳:2022-04-12 11:56:45,使用时此值不可修改
*/
private final static long START_STAMP = 1649735805910L;
有关雪花算法的核心原理,以及如何实现在分布式场景下做到id唯一并且整体呈现递增趋势,会在后续的拓展篇中详细介绍,这里就不再赘述了,我们先把工具类和实体类的源码撸完。
(2)为了防止每次使用SnowFlake类时都会新建一个对象,这里,在io.binghe.shop.utils.id包下新建SnowFlakeFactory类,作为SnowFlake的简单工厂类,在SnowFlakeFactory类中,主要是定义了一个ConcurrentMap类型的成员变量snowFlakeCache用来缓存SnowFlake类的对象,这样就不用在使用SnowFlake类时,每次都要新建一个类对象了。
也许有小伙伴会问:不就是新建一个对象嘛,为啥还要缓存起来呢。
其实,在普通场景下,新建不新建对象,缓存不缓存对象几乎没啥影响,但是在高并发、大流量的场景下,尤其是冰河经历过了高并发、大流量的秒杀系统,如果每次都创建对象的话,系统的性能与资源损耗还是比较大的。
SnowFlakeFactory类的源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 雪花算法工厂
*/
public class SnowFlakeFactory {
/**
* 默认数据中心id
*/
private static final long DEFAULT_DATACENTER_ID = 1;
/**
* 默认的机器id
*/
private static final long DEFAULT_MACHINE_ID = 1;
/**
* 默认的雪花算法句柄
*/
private static final String DEFAULT_SNOW_FLAKE = "snow_flake";
/**
* 缓存SnowFlake对象
*/
private static ConcurrentMap<String, SnowFlake> snowFlakeCache = new ConcurrentHashMap<>(2);
public static SnowFlake getSnowFlake(long datacenterId, long machineId) {
return new SnowFlake(datacenterId, machineId);
}
public static SnowFlake getSnowFlake() {
return new SnowFlake(DEFAULT_DATACENTER_ID, DEFAULT_MACHINE_ID);
}
public static SnowFlake getSnowFlakeFromCache() {
SnowFlake snowFlake = snowFlakeCache.get(DEFAULT_SNOW_FLAKE);
if(snowFlake == null) {
snowFlake = new SnowFlake(DEFAULT_DATACENTER_ID, DEFAULT_MACHINE_ID);
snowFlakeCache.put(DEFAULT_SNOW_FLAKE, snowFlake);
}
return snowFlake;
}
/**
* 根据数据中心id和机器id从缓存中获取全局id
* @param dataCenterId: 取值为1~31
* @param machineId: 取值为1~31
*/
public static SnowFlake getSnowFlakeByDataCenterIdAndMachineIdFromCache(Long dataCenterId, Long machineId) {
if (dataCenterId > SnowFlake.getMaxDataCeneterNum() || dataCenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > SnowFlake.getMaxMachineNum() || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
String key = DEFAULT_SNOW_FLAKE.concat("_").concat(String.valueOf(dataCenterId)).concat("_").concat(String.valueOf(machineId));
SnowFlake snowFlake = snowFlakeCache.get(key);
if(snowFlake == null) {
snowFlake = new SnowFlake(dataCenterId, machineId);
snowFlakeCache.put(key, snowFlake);
}
return snowFlake;
}
}
在SnowFlakeFactory类中,主要对外提供了两个获取SnowFlake的方法,一个是getSnowFlakeFromCache()方法,另一个是getSnowFlakeByDataCenterIdAndMachineIdFromCache()方法。
- getSnowFlakeFromCache()方法
在snowFlakeCache缓存中获取默认的SnowFlake对象实例,如果对象不存在,则调用SnowFlake类的构造方法,并且传入默认的数据中心id和机器id,将实例化后的SnowFlake对象加入缓存,并且返回SnowFlake对象。源码如下所示。
public static SnowFlake getSnowFlakeFromCache() {
SnowFlake snowFlake = snowFlakeCache.get(DEFAULT_SNOW_FLAKE);
if(snowFlake == null) {
snowFlake = new SnowFlake(DEFAULT_DATACENTER_ID, DEFAULT_MACHINE_ID);
snowFlakeCache.put(DEFAULT_SNOW_FLAKE, snowFlake);
}
return snowFlake;
}
- getSnowFlakeByDataCenterIdAndMachineIdFromCache()方法
getSnowFlakeByDataCenterIdAndMachineIdFromCache()方法提供了两个参数,一个是Long类型的dataCenterId,表示数据中心或者机房的id,一个是Long类型的machineId,表示机器id或者服务所在的服务器id。
在getSnowFlakeByDataCenterIdAndMachineIdFromCache()方法中,会对传入的两个参数进行限制。然后生成缓存SnowFlake对象实例的缓存Key,根据生成的Key到snowFlakeCache缓存中获取SnowFlake对象实例,如果对象实例不存在,则根据传入的dataCenterId和machineId生成SnowFlake对象实例,并放入snowFlakeCache缓存中,最后返回SnowFlake对象实例。源码如下所示。
/**
* 根据数据中心id和机器id从缓存中获取全局id
* @param dataCenterId: 取值为1~31
* @param machineId: 取值为1~31
*/
public static SnowFlake getSnowFlakeByDataCenterIdAndMachineIdFromCache(Long dataCenterId, Long machineId) {
if (dataCenterId > SnowFlake.getMaxDataCeneterNum() || dataCenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > SnowFlake.getMaxMachineNum() || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
String key = DEFAULT_SNOW_FLAKE.concat("_").concat(String.valueOf(dataCenterId)).concat("_").concat(String.valueOf(machineId));
SnowFlake snowFlake = snowFlakeCache.get(key);
if(snowFlake == null) {
snowFlake = new SnowFlake(dataCenterId, machineId);
snowFlakeCache.put(key, snowFlake);
}
return snowFlake;
}
(3)为了便于管理每个服务的dataCenterId和machineId,这里将每个服务的dataCenterId和machineId作为配置参数,后续也可以存储到Zookeeper或者Etcd等分布式配置中心。
所以,在io.binghe.shop.utils.id包下新建SnowFlakeLoader类,io.binghe.shop.utils.id.SnowFlakeLoader类的作用主要是加载classpath类路径下的snowflake/snowflake.properties文件,读取dataCenterId和machineId,SnowFlakeLoader类的源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 定义加载params.properties文件的工具类
*/
public class SnowFlakeLoader {
public static final String DATA_CENTER_ID = "data.center.id";
public static final String MACHINE_ID = "machine.id";
private volatile static Properties instance;
static {
InputStream in = SnowFlakeLoader.class.getClassLoader().getResourceAsStream("snowflake/snowflake.properties");
instance = new Properties();
try {
instance.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}
private static String getStringValue(String key){
if(instance == null) return "";
return instance.getProperty(key, "");
}
private static Long getLongValue(String key){
String v = getStringValue(key);
return (v == null || v.trim().isEmpty()) ? 0 : Long.parseLong(v);
}
public static Long getDataCenterId() {
return getLongValue(DATA_CENTER_ID);
}
public static Long getMachineId() {
return getLongValue(MACHINE_ID);
}
}
(4)为了配合SnowFlakeLoader类读取配置文件中的内容,在项目的resources目录下新建snowflake目录,并在snowflake目录下新建snowflake.properties文件,snowflake.properties文件的内容如下所示。
data.center.id=1
machine.id=1
至此,我们项目的通用工具类模块就实现完毕了,后续在开发具体业务时,如果需要扩展,我们在一起扩展通用工具类模块。
另外,源码中提供了针对分布式id的测试用例,可以加入【冰河技术】知识星球获取源码。
创建实体类模块
在父工程下创建实体类模块shop-bean,作为整个项目的通用实体类模块,实体类模块的总体结构如下所示。

添加项目依赖
shop-bean模块的依赖相对来说就比较简单了,只需要依赖shop-utils模块即可。在shop-bean模块的pom.xml文件中添加如下配置。
<dependencies>
<dependency>
<groupId>io.binghe.shop</groupId>
<artifactId>shop-utils</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
核心类开发
对于shop-bean模块来说,主要的功能就是提供JavaBean,目前主要提供四个实体类,分别如下所示。
(1)io.binghe.shop.bean#User类,表示用户类,源码如下所示。
/**
* @author binghe(冰河技术)
* @version 1.0.0
* @description 用户实体类
*/
@Data
@TableName("t_user")
public class User implements Serializable {
private static final long serialVersionUID = -7032479567987350240L;
/**
* 数据id
*/
@TableId(value = "id", type = IdType.INPUT)
@TableField(value = "id", fill = FieldFill.INSERT)
private Long id;
/**
* 用户名
*/
@TableField("t_username")
private String username;
/**
* 密码
*/
@TableField("t_password")
private String password;
/**
* 手机号
*/
@TableField("t_phone")
private String phone;
/**
* 地址
*/
@TableField("t_address")
private String address;
public User(){
this.id = SnowFlakeFactory.getSnowFlakeFromCache().nextId();
//默认密码
this.password = PasswordUtils.getPassowrd("123456");
}
}
(2)io.binghe.shop.bean#Product类,表示商品类,源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 商品
*/
@Data
@TableName("t_product")
public class Product implements Serializable {
private static final long serialVersionUID = -2907409980909070073L;
/**
* 数据id
*/
@TableId(value = "id", type = IdType.INPUT)
@TableField(value = "id", fill = FieldFill.INSERT)
private Long id;
/**
* 商品名称
*/
@TableField("t_pro_name")
private String proName;
/**
* 商品价格
*/
@TableField("t_pro_price")
private BigDecimal proPrice;
/**
* 商品库存
*/
@TableField("t_pro_stock")
private Integer proStock;
public Product(){
this.id = SnowFlakeFactory.getSnowFlakeFromCache().nextId();
}
}
(3)io.binghe.shop.bean#Order类,表示订单类,源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 订单
*/
@Data
@TableName("t_order")
public class Order implements Serializable {
private static final long serialVersionUID = -2907409980909070073L;
/**
* 数据id
*/
@TableId(value = "id", type = IdType.INPUT)
@TableField(value = "id", fill = FieldFill.INSERT)
private Long id;
/**
* 用户id
*/
@TableField("t_user_id")
private Long userId;
/**
* 用户名
*/
@TableField("t_user_name")
private String username;
/**
* 手机号
*/
@TableField("t_phone")
private String phone;
/**
* 地址
*/
@TableField("t_address")
private String address;
/**
* 商品价格(总价)
*/
@TableField("t_total_price")
private BigDecimal totalPrice;
public Order(){
this.id = SnowFlakeFactory.getSnowFlakeFromCache().nextId();
}
}
(4)io.binghe.shop.bean#OrderItem类,表示订单条目类,源码如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 订单明细
*/
@Data
@TableName("t_order_item")
public class OrderItem implements Serializable {
private static final long serialVersionUID = -1329173923755780293L;
/**
* 数据id
*/
@TableId(value = "id", type = IdType.INPUT)
@TableField(value = "id", fill = FieldFill.INSERT)
private Long id;
@TableField("t_order_id")
private Long orderId;
/**
* 商品id
*/
@TableField("t_pro_id")
private Long proId;
/**
* 商品名称
*/
@TableField("t_pro_name")
private String proName;
/**
* 商品价格(单价)
*/
@TableField("t_pro_price")
private BigDecimal proPrice;
/**
* 购买数量
*/
@TableField("t_number")
private Integer number;
public OrderItem(){
this.id = SnowFlakeFactory.getSnowFlakeFromCache().nextId();
}
}
注意:四个实体类都比较简单,小伙伴们可以直接看源码的注释,这里就不再赘述了。同时,这里是为简化商品的下单逻辑而创建的实体类,实际场景下的实体类会远比这些复杂。
创建数据表
这里,主要创建四个数据表,分别为用户表、商品表、订单表和订单条目表,分别对应着四个实体类,如下所示。
- t_user用户表,与User实体类对应

- t_product商品表,与Product实体类对应

- t_order订单表,与Order实体类对应

- t_order_item订单条目表,与OrderItem实体类对应

创建数据表的脚本已经放在项目源码里啦。至此,项目中的通用工具类模块、通用实体类模块就开发完成了,同时,数据表也创建完毕了。下一篇,我们开撸用户微服务、商品微服务和订单微服务。
情趣用品,延时产品,各种都有,添加 微信:yztt15 备注:情趣!
如若转载,请注明出处:https://www.i1026.com/11457.html