前言

最近在学习怎么做一个项目,记录一下一些经常用到的类。

结果类的封装

定义状态码

状态码的定义需要与前端进行约定,这里就定义最简单的两个状态码,成功和失败。

1
2
3
4
5
6
public interface ResultCode {
// 成功
public static Integer SUCCESS = 20000;
// 失败
public static Integer ERROR = 20001;
}

结果类的封装

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
// 使用lombok偷懒,不使用lombok就自己定义get和set方法
@Data
public class Result {
// 是否成功
private Boolean success;
/**
* 状态码
*/
private Integer code;
/**
* 返回内容
*/
private String message;
/**
* 返回数据
*/
private Map<String, Object> data = new HashMap<String, Object>();

/**
* 无参构造函数,将构造方法私有
*/
private Result(){}

// 操作成功时的默认返回值
public static Result ok() {
Result result = new Result();
result.setSuccess(true);
result.setCode(ResultCode.SUCCESS);
result.setMessage("操作成功");
return result;
}

// 操作失败的默认返回值
public static Result error() {
Result result = new Result();
result.setSuccess(false);
result.setCode(ResultCode.ERROR);
result.setMessage("操作失败");
return result;
}

public Result success(Boolean success) {
this.setSuccess(success);
return this;
}
public Result message(String message) {
this.setMessage(message);
return this;
}
public Result code(Integer code) {
this.setCode(code);
return this;
}
public Result data(String key,Object value) {
this.data.put(key, value);
return this;
}
public Result data(Map<String,Object> map) {
this.setData(map);
return this;
}

}

使用结果类

这里使用mybatis-plus来测试一下返回的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class EduTeacherController {

@Autowired
private CourseService courseService;

// 返回查询到的数据
@GetMapping("findAll")
public Result findAllCourse() {
List<Course> list = courseService.list(null);
if(list == null) {
return Result.error().message("没有查询到任何数据");
} else {
return Result.ok().data("courseList",list);
}
}
}

可以看到成功后的结果。

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
{
"success": true,
"code": 20000,
"message": "操作成功",
"data": {
"courseList": [
{
"id": "1272143086562152450",
"title": "语文",
"gmtCreate": "2020-06-14 20:25:22",
"gmtModified": "2020-06-15 20:29:19"
},
{
"id": "1272143133739683841",
"title": "数学",
"gmtCreate": "2020-06-14 20:25:33",
"gmtModified": "2020-06-15 20:29:33"
},
{
"id": "1272143217621569537",
"title": "英语",
"gmtCreate": "2020-06-14 20:25:53",
"gmtModified": "2020-06-15 20:29:05"
}
]
}
}

返回这样的JSON数据,前端就能更好的使用接口得到的数据,而且统一结果类后,所有的controller的返回值都使用这个结果类返回,从而规范返回的结果。

自定义全局异常类

定义一个异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @ClassName TestException
* @Descriprtion 自定义异常,继承RuntimeException异常类
* 该类可以做成枚举类型
* @Author yww
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestException extends RuntimeException{

private Integer code;

private String msg;

}

设置异常处理

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
/**
* @ClassName GlobalExceptionHandler
* @Descriprtion 统一异常处理类
* @Author yww
**/

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e) {
e.printStackTrace();
return Result.error().message("执行全局异常处理!");
}

@ExceptionHandler(TestException.class)
@ResponseBody
public Result error(schoolException e) {
log.error(e.getMsg());
e.printStackTrace();
return Result.error().code(e.getCode()).message(e.getMsg());
}

}

处理逻辑,从小到大。 即出现TestException就会优先使用第二个方法处理异常。 出现了其他的异常,就会使用大一级的异常。

测试

写一个测试类来测试一下。

1
2
3
4
5
@Test
public void run() {
// 自定义返回结果码和异常信息
throw new TestException(20001, "出现TestException异常");
}
1
TestException(code=20001, msg=出现TestException异常)

这样返回异常信息给前端,前端就能展示给用户,从而用户就知道怎样的操作是不合理的了。

MD5加密

用户的密码一般都会进行加密后在存储到数据库当中,所以一般用到MD5这种加密算法。

这种加密算法最大的特点就是不可逆,只能加密不能解密。

这是挺简单的一种加密算法了,之后还可以考虑加盐和哈希散列,进行更复杂的加密手段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Md5Utils {
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错+" + e);
}
}
}

Redis配置类

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
/**
* @ClassName RedisConfig
* @Descriprtion Redis的配置类
* @Author yww
**/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurationSelector {

/**
* 自定义RedisTemplate
* @param redisConnectionFactory Redis工厂
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 连接工厂
template.setConnectionFactory(redisConnectionFactory);
// JSON序列化的配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);

// String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value采用Jackson的JSON序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value采用Jackson的JSON序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);

template.afterPropertiesSet();

return template;
}

/**
* 缓存管理
* @param factory Redis连接工厂
* @return CacheManager
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();
return cacheManager;
}

}

雪花算法

对于MySQL的主键问题,一般都需要使用自增的主键。但是只是从1自增就会因为分库分表产生主键相同的情况。

所以这次使用雪花算法生成一个分布式全局唯一ID

首先要知道雪花算法生成的ID是怎么样的。

分布式ID一般都是Long类型,所以会有64位。

  • 第一部分是一个bit位,是一个标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以固定为0
  • 第二部分是时间戳,该部分占41bit,这个是毫秒级的时间
  • 第三部分是工作机器ID,占10bit
  • 第四部分是序列号,占12bit,支持同一毫秒内同一个节点可以生成4096个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
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
/**
* <p>
* 雪花算法,分布式全局唯一ID
* </p>
* @ClassName SnowFlake
* @Author yww
* @date 2021/4/24 18:39
**/
public class SnowFlake {

/**
* 起始的时间戳
*/
private final static long START_STMP = 1480166465631L;
/**
* 序列号占用的位数
*/
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_STMP) << 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 void main(String[] args) {

SnowFlake snowFlake = new SnowFlake(2, 3);

for (int i = 0; i < (1 << 12); i++) {
System.out.println(snowFlake.nextId());
}

}


}

参考于:https://github.com/beyondfengyu/SnowFlake