十次方项目-day01

  学了好长时间了,应该认真分析一个项目,了解一个项目的开发流程、顺序、模式等

第一章 - 项目搭建

1 系统设计

1.1 开发模式

采用前后端分离的开发模式

1.2 技术选型

前端:nodejs+NUXT+elementUI+vue

后端:springboot+springcloud+mybatis plus + mysql 5.7

1.3 技术架构

前后端分离的系统架构

1.4 微服务模块划分

模块名称 模块中文名称
tensquare_common 公共模块
tensquare_base 基础微服务
tensquare_article 文章微服务
tensquare_friend 交友微服务
tensquare_gathering 活动微服务
tensquare_qa 问答微服务
tensquare_recruit 招聘微服务
tensquare_user 用户微服务
tensquare_spit 吐槽微服务
tensquare_search 搜索微服务
tensquare_web 前台微服务网关
tensquare_manager 后台微服务网关
tensquare_eureka 注册中心
tensquare_config 配置中心
tensquare_sms 短信微服务
tensquare_notice 消息通知微服务

1.5 建数据库表结构分析

采用的分库分表设计,每个微服务模块为1个独立的数据库。

​ tensquare_article 文章

​ tensquare_base 基础

​ tensquare_friend 交友

​ tensquare_gathering 活动

​ tensquare_qa 问答

​ tensquare_recruit 招聘

​ tensquare_user 用户

​ tensquare_spit 吐槽

1.6 API文档

前后端约定的返回码列表:

状态描述 返回码
成功 20000
失败 20001
用户名或密码错误 20002
权限不足 20003
远程调用失败 20004
重复操作 20005

2 接口规范

十次方项目使用GET、POST、PUT、DELETE四种方法

​ 幂等性:不论你请求多少次,资源的状态是一样的。

2.1 GET

  • 安全且幂等
  • 获取表示
  • 变更时获取表示(缓存)

==适合查询类的接口使用==

2.2 POST

  • 不安全且不幂等
  • 使用服务端管理的(自动产生)的实例号创建资源
  • 创建子资源
  • 部分更新资源
  • 如果没有被修改,则不过更新资源(乐观锁)

==适合数据提交类的接口使用==

2.3 PUT

  • 不安全但幂等
  • 用客户端管理的实例号创建一个资源
  • 通过替换的方式更新资源
  • 如果未被修改,则更新资源(乐观锁)

    ==适合更新数据的接口使用==

2.4 DELETE

  • 不安全但幂等
  • 删除资源

==适合删除数据的接口使用==

请求返回响应码:

代码 含义
==200== (OK)- 如果现有资源已被更改
201 (created)- 如果新资源被创建
202 (accepted)- 已接受处理请求但尚未完成(异步处理)
301 (Moved Permanently)- 资源的URI被更新
303 (See Other)- 其他(如,负载均衡)
==400== (bad request)- 指代坏请求
==404== (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
==500== (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求

3 项目开发准备

3.1 开发环境

  • 虚拟系统环境 VMware Workstation
  • 虚拟机系统 CentOS 7
  • 容器 docker
  • JDK1.8
  • 数据库 mysql 5.7
  • 开发工具 idea
  • 项目构建工具 maven

3.2 mysql建库建表

所有的第三方工具如mysql等都是运行在docker容器中的
注:虚拟机的帐户名root 密码itcast

进入安装了docker的虚拟机中,按以下顺序执行命令

(1)下载镜像(此步可省略)

1
docker pull centos/mysql-57-centos7

注:docker默认从国外的镜像网站拉取镜像,速度很慢。可以使用国内的阿里云镜像加速站点提升镜像拉取速度。具体步骤可以参考文档

docker配置国内镜像加速站点.pdf

(2)创建容器

scYR1O.jpg

scYa1U.md.jpg

scYwX4.jpg

scYBnJ.md.jpg

1
2
docker run -di --name=tensquare_mysql -p 3306:3306 -e
MYSQL_ROOT_PASSWORD=root centos/mysql-57-centos7

(3)连接MYSQL ,并执行资料中的建表脚本,创建article数据库

3.3 接口测试工具postman

postman是一款强大网页调试工具

  • 能够发送任何类型的HTTP 请求 (GET,HEAD, POST,PUT…)
  • 附带任意数量的参数

4 项目工程搭建

4.1 父工程搭建

创建项目类型为maven的父工程

1.选择file-new project,选择maven,点击next

scYDB9.md.png

2.GroupId填写com.tensquare,ArtifacetId填写tensquare_parent,点击next

scYdcF.md.png

3.点击Finash完成

4.修改pom.xml文件,添加以下内容

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<mybatisplus-spring-bootstarter.version>1.0.5</mybatisplus-spring-bootstarter.version>
<mybatisplus.version>2.2.0</mybatisplus.version>
<fastjson.version>1.2.39</fastjson.version>
<gson.version>2.8.0</gson.version>
</properties>

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

4.2 搭建公共子模块

4.2.1 搭建子模块步骤

1.右键点击父工程tensquare_parent,选择New->Module弹出窗口选择Maven,点击next

2.ArtifacetId填写tensquare_common,点击next

scYr7R.md.png

3.点击finish

4.2.1 创建公共实体类和工具类

1.新建com.tensquare.entity包,包下创建Result类,用于controller返回结果

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
package entity;

public class Result {
private Boolean flag;//是否成功
private Integer code;//返回码
private String message;//返回信息
private Object data;//返回数据

public Result() {
}

public Result(Boolean flag, Integer code, String message) {
this.flag = flag;
this.code = code;
this.message = message;
}

@Override
public String toString() {
return "Result{" +
"flag=" + flag +
", code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}

public Boolean getFlag() {
return flag;
}

public void setFlag(Boolean flag) {
this.flag = flag;
}

public Integer getCode() {
return code;
}

public void setCode(Integer code) {
this.code = code;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

public Result(Boolean flag, Integer code, String message, Object data) {
this.flag = flag;
this.code = code;
this.message = message;
this.data = data;
}
}

2.创建类PageResult,用于返回分页结果

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
package entity;

import java.util.List;

public class PageResult<T> {
private Long total;
private List<T> rows;

@Override
public String toString() {
return "PageResult{" +
"total=" + total +
", rows=" + rows +
'}';
}

public PageResult() {
}

public PageResult(Long total, List<T> rows) {
this.total = total;
this.rows = rows;
}

public Long getTotal() {
return total;
}

public void setTotal(Long total) {
this.total = total;
}

public List<T> getRows() {
return rows;
}

public void setRows(List<T> rows) {
this.rows = rows;
}
}

3.返回码定义类

1
2
3
4
5
6
7
8
9
10
package entity;

public class StatusCode {
public static final int OK = 20000;//成功
public static final int ERROR = 20001;//失败
public static final int LOGINERROR = 20002;//用户名或密码错误
public static final int ACCESSERROR = 20003;//权限不足
public static final int REMOTEERROR = 20004;//远程调用失败
public static final int REPERROR = 20005;//重复操作
}

4.分布式ID生成器

附录/IdWorker.java

tensquare_common工程创建util包,将IdWorker.java直接拷贝到
tensquare_common工程的util包中。

不能使用数据库本身的自增功能来产生主键值,原因是生产环境为分片部署的。
使用snowflake (雪花)算法(twitter出品)生成唯一的主键值

scY29K.md.png

  • 41bit的时间戳可以支持该算法使用到2082年
  • 10bit的工作机器id可以支持1024台机器
  • 序列号支持1毫秒产生4096个自增序列id
  • 整体上按照时间自增排序
  • 整个分布式系统内不会产生ID碰撞
  • 每秒能够产生26万ID左右

5 文章微服务-文章管理

5.1 模块搭建

  1. 在tensquare_parent项目下创建tensquare_article模块,创建过程参考第4.2节
    公共子模块的创建过程
  2. 修改tensquare_article模块的pom.xml文件,修改添加以下依赖
1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>com.tensquare</groupId>
<artifactId>tensquare_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

3.创建com.tensquare.article包,并创建ArticleApplication类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.tensquare.article;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import util.IdWorker;

@SpringBootApplication
//配置Mapper包扫描
@MapperScan("com.tensquare.article.dao")
public class ArticleApplication {

public static void main(String[] args) {
SpringApplication.run(ArticleApplication.class, args);
}

@Bean
public IdWorker createIdWorker() {
return new IdWorker(1, 1);
}
}

4.在resources文件夹下创建application.yml,并添加以下内容

1
2
3
4
5
6
7
8
9
10
server:
port: 9004 # 服务访问端口号
spring:
application:
name: tensquare-article #服务名称
datasource: # 数据库连接四大属性
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.129:3306/tensquare_article?characterEncoding=utf-8
username: root
password: root

5.2 文章管理-CRUD

5.2.1 表结构分析

使用Navicat连接虚拟机建库

tensquare_article数据库,tb_article表

文章表 tb_article
字段名称 字段含义 字段类型 备注
id ID 文本
columnid 专栏ID 文本
userid 用户ID 文本
title 文章标题 文本
content 文章内容 文本
image 文章封面 文本
createtime 发表日期 日期
updatetime 修改日期 日期
ispublic 是否公开 文本 0:不公开
istop 是否置顶 文本 0:不置顶
visits 浏览量 整型
thumbup 点赞数 整型
comment 评论数 整型
state 审核状态 文本 0:未审核 1:已审核
channelid 所属频道 整型 关联频道表ID
url URL地址 文本
type 文章类型 文本 0:分享

5.2.2 集成mybatisplus

1.在pom.xml文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- mybatis-plus begin -->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatisplus-spring-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.5</version>
</dependency>


<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<!-- mybatis-plus end -->

2.在之前的application.yml文件中添加相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Mybatis-Plus 配置
mybatis-plus:
# mapper-locations: classpath:/mapper/*Mapper.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.tensquare.article.pojo
global-config:
id-type: 1 #0:数据库ID自增 1:用户输入id
db-column-underline: false
refresh-mapper: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: true #配置的缓存的全局开关
lazyLoadingEnabled: true #延时加载的开关
multipleResultSetsEnabled: true #开启延时加载,否则按需加载属性
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句,调试用

3.在启动类添加Mapper扫描注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
//配置Mapper包扫描
@MapperScan("com.tensquare.article.dao")
public class ArticleApplication {

public static void main(String[] args) {
SpringApplication.run(ArticleApplication.class, args);
}

@Bean
public IdWorker createIdWorker() {
return new IdWorker(1, 1);
}
}

5.2.3 查询所有文章和根据id号查询文章功能

1.在com.tenquare.article包下常见pojo包,并创建Article实体类

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
package com.tensquare.article.pojo;

import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;

import java.io.Serializable;
import java.util.Date;

@TableName("tb_article")

public class Article implements Serializable {

@TableId(type = IdType.INPUT)
private String id;//ID
private String columnid; //专栏ID
private String userid; //用户ID
private String title; //标题
private String content; //文章正文
private String image; //文章封面
private Date createtime; //发表日期
private Date updatetime; //修改日期
private String ispublic; //是否公开
private String istop; //是否置顶
private Integer visits; //浏览量
private Integer thumbup; //点赞数
private Integer comment; //评论数
private String state; //审核状态
private String channelid; //所属频道
private String url; //URL
private String type; //类型

//getters and setters and toString
}

2.编写Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("article")
public class ArticleController{

@Autowired
private ArticleService articleService;

//查询所有文章
@GetMapping
public Result findAll(){
List list = articleService.findAll();
return new Rusult(true,StatusCode.OK,"查询成功",list);
}

//根据id查询文章
@GetMapping("{articleId}")
public Result findById(@PathVariable String articleId){
Article article = articleService.findById(articleId);
return new Result(true,Status.OK,"查询成功",article);
}
}

3.编写dao接口

1
public interface AricleDao extends BaseMapper<Article>{}

4.编写service层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class ArticleService{
@Autowried
private ArticleDao articleDao;

//查询所有文章
public List<Article> findAll() {
Article article = articleDao.selectById(1);
return articleDao.selectList(null);
}

//根据ID查询
public Article findById(String articleId){
return articleDao.selectById(articleId);
}
}

5.2.4 添加文章、删除文章、修改文章

在相应层写入以下代码

1.Controller类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//新增文章
@PostMapping
public Result save(@RequestBody Article article) {
articleService.save(article);
return new Result(true, StatusCode.OK, "新增成功");
}

// 修改文章
@PutMapping(value = "{articleId}")
public Result updateById(@PathVariable String articleId,
@RequestBody Article article) {
// 设置id
article.setId(articleId);
// 执行修改
articleService.updateById(article);
return new Result(true, StatusCode.OK, "修改成功");
}

// 删除文章
@DeleteMapping(value = "{articleId}")
public Result deleteById(@PathVariable String articleId) {
articleService.deleteById(articleId);
return new Result(true, StatusCode.OK, "删除成功");
}

2.service类

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
@Autowired
private IdWorker idWorker;

public void save(Article article) {
//使用分布式id生成器
String id = idWorker.nextId() + "";
article.setId(id);

//初始化点赞,浏览量,评论数据
article.setVisits(0);
article.setComment(0);
article.setThumbup(0);

//新增
articleDao.insert(article);
}

public void updateById(Article article) {
//有以下两种方法
// * 根据主键修改
articleDao.updateById(article);

// * 根据条件修改
// 创建条件对象
// EntityWrapper<Article> wrapper = new EntityWrapper<>();
// 设置条件
// wrapper.eq("id", article.getId());
// articleDao.update(article, wrapper);
}

public void deleteById(String articleId) {
articleDao.deleteById(articleId);
}

5.2.5 条件查询和分页

1.条件查询

使用MybatisPlus提供的EntityWrapper对象封装的where查询条件,例如以下使用方式:

1
2
3
4
5
6
7
// if (map.get(key) != null) {
// wrapper.eq(key, map.get(key));
// }
// 或
//第一个参数是否把后面的条件加入到查询条件中
//和上面的if判断的写法是一样的效果,实现动态sql
wrapper.eq(map.get(key) != null, key, map.get(key));

2.分页

  • 使用 Mybatis Plus 提供的Page对象
  • 向Mybatis Plus中注入PaginationInterceptor插件
  • 新建config包,创建MybatisPlusConfig对象,添加下面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.tensquare.article.config;

import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {

@Bean
public PaginationInterceptor createPaginationInterceptor(){
return new PaginationInterceptor();
}
}

3.完整代码

AricleController中添加代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 文章分页
@PostMapping(value = "search/{page}/{size}")
// 之前接受文章数据使用pojo,但是现在是条件查询
// 而所有的条件都需要进行判断,遍历pojo的所有属性要使用反射的方式不推荐
// 直接使用集合的方式遍历,这里接收数据改为Map集合
public Result findByPage(@PathVariable Integer page,
@PathVariable Integer size,
@RequestBody Map<String, Object> map) {

//根据条件分页查询
Page<Article> pageData = articleService.findByPage(map, page, size);
//封装分页返回对象
PageResult<Article> pageResult = new PageResult<>(pageData.getTotal(), pageData.getRecords());
//返回数据
return new Result(true, StatusCode.OK, "条件查询成功",pageResult);
}

ArtcleService中代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Page<Article> findByPage(Map<String, Object> map, Integer page, Integer size) {
// 设置查询条件
EntityWrapper<Article> wrapper = new EntityWrapper<>();
Set<String> keySet = map.keySet();
for (String key : keySet) {
// if (map.get(key) != null) {
// wrapper.eq(key, map.get(key));
// }
//第一个参数是否把后面的条件加入到查询条件中
//和上面的if判断的写法是一样的效果,实现动态sql
wrapper.eq(map.get(key) != null, key, map.get(key));
}
//设置分页参数
Page<Article> pageData = new Page<>(page, size);
// 执行查询
// 第一个是分页参数,第二个是查询条件
List<Article> list = articleDao.selectPage(pageData, wrapper);
pageData.setRecords(list);
// 返回
return pageData;
}

附录

1 IdWorker.java

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package util;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

/**
* <p>名称:IdWorker.java</p>
* <p>描述:分布式自增长ID</p>
* <pre>
* Twitter的 Snowflake JAVA实现方案
* </pre>
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
* 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
* 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
* 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
* 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
* 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
* 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
* <p>
* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
* @author Polim
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;

private final long workerId;
// 数据标识id部分
private final long datacenterId;

public IdWorker() {
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}

/**
* @param workerId 工作机器ID
* @param datacenterId 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}

/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}

if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;

return nextId;
}

private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}

private long timeGen() {
return System.currentTimeMillis();
}

/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}

/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
}