# SA实战 ·《SpringCloud Alibaba实战》第05章-完成通用模块的开发

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)

大家好,我是冰河~~

从今天开始,我们正式进入《SpringCloud Alibaba实战 (opens new window)》专栏撸源码的环节。根据前面文章的描述,我们采用Maven来管理整个项目的结构,主要使用SpringBoot+SpringCloud Alibaba技术栈实现。今天,是我们撸源码的第一天,我们先使用SpringBoot来快速搭建项目并且完成通用工具类和实体类的开发。

# 项目总体结构

项目总体上包含一个Maven父工程,实体类模块、工具类模块、用户微服务、商品微服务和订单微服务都以Maven子模块的形式存在,项目总体结果如下所示。

sa-2022-04-18-001

其中每个部分的含义如下所示。

其中各模块的说明如下所示:

  • 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 创建工具类模块

在父工程下创建工具类模块shop-utils,作为整个项目的通用工具类模块。工具类模块的总体结构如下所示。

sa-2022-04-18-002

# 添加项目依赖

在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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 核心类开发

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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());
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

这里,需要注意的是:在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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

根据雪花算法的实现可以发现,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;
}
1
2
3
4
5
6
7
8
9
10

其中,第一个参数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;
1
2
3
4

有关雪花算法的核心原理,以及如何实现在分布式场景下做到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;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

在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;
}
1
2
3
4
5
6
7
8
  • 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

(4)为了配合SnowFlakeLoader类读取配置文件中的内容,在项目的resources目录下新建snowflake目录,并在snowflake目录下新建snowflake.properties文件,snowflake.properties文件的内容如下所示。

data.center.id=1
machine.id=1
1
2

至此,我们项目的通用工具类模块就实现完毕了,后续在开发具体业务时,如果需要扩展,我们在一起扩展通用工具类模块。

另外,源码中提供了针对分布式id的测试用例,可以加入【冰河技术】知识星球获取源码。

# 创建实体类模块

在父工程下创建实体类模块shop-bean,作为整个项目的通用实体类模块,实体类模块的总体结构如下所示。

sa-2022-04-18-003

# 添加项目依赖

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>
1
2
3
4
5
6
7

# 核心类开发

对于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");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

(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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

(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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

(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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

注意:四个实体类都比较简单,小伙伴们可以直接看源码的注释,这里就不再赘述了。同时,这里是为简化商品的下单逻辑而创建的实体类,实际场景下的实体类会远比这些复杂。

# 创建数据表

这里,主要创建四个数据表,分别为用户表、商品表、订单表和订单条目表,分别对应着四个实体类,如下所示。

  • t_user用户表,与User实体类对应

sa-2022-04-18-004

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

sa-2022-04-18-005

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

sa-2022-04-18-006

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

sa-2022-04-18-007

至此,项目中的通用工具类模块、通用实体类模块就开发完成了,同时,数据表也创建完毕了。下一篇,我们开撸用户微服务、商品微服务和订单微服务。

# 知识星球

另外,冰河开通了知识星球——冰河技术(与公号同名),整体项目的源代码会发布到知识星球,并且针对项目的答疑也会在知识星球进行。

同时,知识星球会优先发布冰河的直播、视频与文章信息,优先回答知识星球内的提问。另外,知识星球也会提供一对一答疑服务,提供简历修改和面试技巧等帮助。还会分享冰河 从程序员一路成长到互联网资深技术专家 的历程与学习路线,后续还会分享冰河是如何 从计算机小白成长为渗透专家(很多人管这个也叫黑客,不对外公开),让加入星球的小伙伴真正做到少走弯路。

在【冰河技术】知识星球,你学到的不只是技术。好啦,由于冰河刚刚开通了【冰河技术】知识星球,现针对加入知识星球的新人推出了优惠活动。目前知识星球的总价是50元(貌似最低只能设置到50元),冰河给出了25元立减金优惠券,仅限前50名使用,如下所示。

sa-2022-04-18-008

另外,加入星球的小伙伴再加冰河微信 hacker_binghe,私信冰河再返5元红包。最后,你在【冰河技术】知识星球学到的不只是技术

好了,今天就到这儿吧,我是冰河,我们下期见~~

# 星球服务

加入星球,你将获得:

1.项目学习:微服务入门必备的SpringCloud Alibaba实战项目、手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】、Seckill秒杀系统项目—进大厂必备高并发、高性能和高可用技能。

2.框架源码:手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】。

3.硬核技术:深入理解高并发系列(全册)、深入理解JVM系列(全册)、深入浅出Java设计模式(全册)、MySQL核心知识(全册)。

4.技术小册:深入理解高并发编程(第1版)、深入理解高并发编程(第2版)、从零开始手写RPC框架、SpringCloud Alibaba实战、冰河的渗透实战笔记、MySQL核心知识手册、Spring IOC核心技术、Nginx核心技术、面经手册等。

5.技术与就业指导:提供相关就业辅导和未来发展指引,冰河从初级程序员不断沉淀,成长,突破,一路成长为互联网资深技术专家,相信我的经历和经验对你有所帮助。

冰河的知识星球是一个简单、干净、纯粹交流技术的星球,不吹水,目前加入享5折优惠,价值远超门票。加入星球的用户,记得添加冰河微信:hacker_binghe,冰河拉你进星球专属VIP交流群。

# 星球重磅福利

跟冰河一起从根本上提升自己的技术能力,架构思维和设计思路,以及突破自身职场瓶颈,冰河特推出重大优惠活动,扫码领券进行星球,直接立减149元,相当于5折, 这已经是星球最大优惠力度!


领券加入星球,跟冰河一起学习《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》,更有已经上新的《大规模分布式Seckill秒杀系统》,从零开始介绍原理、设计架构、手撸代码。后续更有硬核中间件项目和业务项目,而这些都是你升职加薪必备的基础技能。

100多元就能学这么多硬核技术、中间件项目和大厂秒杀系统,如果是我,我会买他个终身会员!

# 其他方式加入星球

特别提醒: 苹果用户进圈或续费,请加微信 hacker_binghe 扫二维码,或者去公众号 冰河技术 回复 星球 扫二维码加入星球。

# 星球规划

后续冰河还会在星球更新大规模中间件项目和深度剖析核心技术的专栏,目前已经规划的专栏如下所示。

# 中间件项目

  • 《大规模分布式定时调度中间件项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式IM(即时通讯)项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式网关项目实战(非Demo)》:全程手撸代码。
  • 《手写Redis》:全程手撸代码。
  • 《手写JVM》全程手撸代码。

# 超硬核项目

  • 《从零落地秒杀系统项目》:全程手撸代码,在阿里云实现压测(已上新)。
  • 《大规模电商系统商品详情页项目》:全程手撸代码,在阿里云实现压测。
  • 其他待规划的实战项目,小伙伴们也可以提一些自己想学的,想一起手撸的实战项目。。。

既然星球规划了这么多内容,那么肯定就会有小伙伴们提出疑问:这么多内容,能更新完吗?我的回答就是:一个个攻破呗,咱这星球干就干真实中间件项目,剖析硬核技术和项目,不做Demo。初衷就是能够让小伙伴们学到真正的核心技术,不再只是简单的做CRUD开发。所以,每个专栏都会是硬核内容,像《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》就是很好的示例。后续的专栏只会比这些更加硬核,杜绝Demo开发。

小伙伴们跟着冰河认真学习,多动手,多思考,多分析,多总结,有问题及时在星球提问,相信在技术层面,都会有所提高。将学到的知识和技术及时运用到实际的工作当中,学以致用。星球中不少小伙伴都成为了公司的核心技术骨干,实现了升职加薪的目标。

# 联系冰河

# 加群交流

本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “冰河” 微信(hacker_binghe),备注:星球编号

冰河微信

# 公众号

分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。内容在 冰河技术 微信公众号首发,强烈建议大家关注。

公众号:冰河技术

# 视频号

定期分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。

视频号:冰河技术

# 星球

加入星球 冰河技术 (opens new window),可以获得本站点所有学习内容的指导与帮助。如果你遇到不能独立解决的问题,也可以添加冰河的微信:hacker_binghe, 我们一起沟通交流。另外,在星球中不只能学到实用的硬核技术,还能学习实战项目

关注 冰河技术 (opens new window)公众号,回复 星球 可以获取入场优惠券。

知识星球:冰河技术