Skip to content

Commit

Permalink
add designs
Browse files Browse the repository at this point in the history
  • Loading branch information
rbmonster committed Jan 24, 2021
1 parent fca1bf9 commit 40e040e
Show file tree
Hide file tree
Showing 20 changed files with 1,446 additions and 3 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
- [Redis数据结构的设计应用](https://github.com/rbmonster/learning-note/blob/master/src/main/java/com/toc/REDIS_APPLICATION.md)
- [敏感数据过滤设计](https://github.com/rbmonster/learning-note/blob/master/src/main/java/com/toc/SENSITIVE_FILTER.md)
- [数据库读写分离](https://github.com/rbmonster/learning-note/blob/master/src/main/java/com/toc/READ_WRITE_DB.md)
- [数据库表相关设计题](https://github.com/rbmonster/learning-note/blob/master/src/main/java/com/toc/TABLE_DESIGN.md)
- [会议系统设计](https://github.com/rbmonster/learning-note/blob/master/src/main/java/com/toc/MEETING_DESIGN.md)

## 分布式与微服务
- [Spring Cloud相关知识](https://github.com/rbmonster/learning-note/blob/master/src/main/java/com/toc/SPRING-CLOUD.md)
Expand All @@ -64,7 +66,7 @@
- [设计模式(head first)](https://github.com/rbmonster/learning-note/blob/master/src/main/java/com/toc/CODEDESIGN_BOOK.md)
- [JVM基础(周志明)](https://github.com/rbmonster/learning-note/tree/master/src/main/java/com/toc/JVM_BOOK.md)
- [Redis基础(基于Redis的设计与实现)](https://github.com/rbmonster/learning-note/tree/master/src/main/java/com/toc/REDIS_BOOK.md)
- [MySql45讲](https://github.com/rbmonster/learning-note/tree/master/src/main/java/com/toc/MYSQL_BOOK.md)
- [MySql45讲](https://github.com/rbmonster/learningA-note/tree/master/src/main/java/com/toc/MYSQL_BOOK.md)
- [java集合类(Java编程思想)](https://github.com/rbmonster/learning-note/tree/master/src/main/java/com/toc/COLLECTION_BOOK.md)
- [java并发(Java编程思想)](https://github.com/rbmonster/learning-note/tree/master/src/main/java/com/toc/CONCURRENT_BOOK.md)

Expand Down
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,16 @@
<version>2.4.7</version>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.roaringbitmap</groupId>
<artifactId>shims</artifactId>
<version>0.7.45</version>
</dependency>
</dependencies>


Expand Down
101 changes: 101 additions & 0 deletions src/main/java/com/design/MEETING_DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 会议室预约系统

会议室预约基本元素:
1. 会议室:会议室ID、可容纳人数、是否多媒体等等
2. 日期
3. 基本信息:预约人等

## 常规思路
会议室预约是一个时间段,包括开始时间、结束时间。

### 数据库设计
1. 会议室信息主表。
2. 人员信息表
3. 预约信息主表、子表


- 以下为预约信息主表`t_reserve`及子表 `t_reserve_detail`

|| 类型 | 描述 |
| --- | --- | ---|
|id|int64|自增键|
|meeting_id|int64|索引,会议表的id|
|date_month|date|索引,会议室日期,形如2019-02-18|

|| 类型 | 描述 |
| --- | --- | ---|
|id|int64|自增键|
|reserve_id|int64|索引,预约主表ID|
|start_time|date|开始时间,形如2019-02-18 12:00|
|end_time|date|开始时间,形如2019-02-18 12:00|

### 处理逻辑

#### 查询逻辑
查询某天某个时间段空闲会议室:
```
select t.meeting_id from t_reserve left t join t_reserve_detail td on t.id = t.reserve_id
where t.date_month = xxx and not exists
(
select reserve_id from t_reserve_detail td1
where start_time between '2019-07-27 00:00:00' and '2019-07-29 00:00:00' and td1.reserve_id = t.id
union
select reserve_id from t_reserve_detail td2
where end_time between '2019-07-27 00:00:00' and '2019-07-29 00:00:00'
and td2.reserve_id = t.id
)
```

#### 会议室预约路基
1. 查询选定时间段有的会议室
2. 选定会议室
3. 选定时间段
4. 进行主子表的信息插入。

#### 并发控制
1. 单体应用使用java锁进行控制
2. 集群或者分布式应用使用数据库乐观锁或者redis 分布式锁控制

#### 问题点
1. 查询的性能问题
2. 并发控制的性能问题:
1. 使用数据库乐观锁,用户体验问题。
2. redis分布式锁,key:会议ID+日期,一个会议室不同时间段预约不冲突的情况,也需要获取锁。

## 改进思路

1. 查询效率优化
2. 并发控制优化

实际的会议室预约中,不开始时间及结束时间会精细到具体的分秒。可以与产品沟通将需求确定到以5min、10min、15min的粒度。

### 查询优化
1. 根据5min将一天24h进行划分
- 24*60/5 = 288
2. 主表上新增区间字段,代表当前区间是否有会议室预约
- 假设以2h为刻度,那么新增12个字段区间 T1 ~ T12。
-`位0、1` 代表当前5min刻度是否有人预约


实际查询过程:根据起始时间和结束时间换算对应的区间
```
场景一: 同一个区间
startTime = 12:00
endTime = 12:40
区间换算 => 第7个区间(T7)
select meeting_id from demo where SUBSTR(T7 FROM 0 FOR 8) = 0
场景二:跨区间
startTime = 11:30 -> T6
endTime = 12:40 -> T7
select meeting_id from demo where T6 < xxx and SUBSTR(T7 FROM 0 FOR 8) = 0
场景三: 跨天 TODO
```

### 并发优化
使用redis分布式锁锁对应的时间区间,减小锁粒度。
34 changes: 34 additions & 0 deletions src/main/java/com/design/TABLE_DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 表结构设计

## 部门表设计
单表的设计方案,使用parent_id做关系连接,形成级联结构。例子:
```
id | parent_id | deparement_name | department_desc| ...
```

问题点:
- 如何根据一个部门查询对应的父部门及子部门?
1. 使用java内存操作,一次性读取所有部门信息,进行级联组装。再通过id查询子节点的所有部门。
2. 添加表字段,添加一个path字段,用于表示父部门的所有路径。

## 签到表设计
用户签到只有两种状态,因此可以使用`位 0 1` 表示签到的状态。使用mask 32位的int 表示每个月签到的次数
1. 获取用户当月的签到状态:
- 根据服务器时间判断当前的月份month,根据传入的user_id和month去数据库中查找用户签到的数据mask。
2. 签到:
- 假设当日是本月第i天(这个可以计算得出),更新数据库中mask: mask = mask | (1 << i),更新连续签到天数,若i是1或第i-1天没有签到,本月连续签到天数置为1;其他情况则更新连续签到天数+1。
3. 判断当日是否签到:
- 如果mask & (1 << i)大于0,说明签到了,如果为0,说明未签到。
4. 本月补签:
- 补签某个日期,假设是第j天,更新数据库中mask: mask = mask | (1 << j),重新统计本月连续签到天数,从第i位开始逆序遍历到第1天统计连续签到天数。


|| 类型 | 描述 |
| --- | --- | ---|
|id|int64|自增键|
|user_id|int64|索引,用户表的id|
|date_month|date|索引,月份,形如2019-02|
|mask|int32|用户签到的数据|
|continue_sign_month|int32|用户本月连续签到的天数|

- 相关资料: [签到设计](https://blog.csdn.net/liyunlong41/article/details/86739134)
160 changes: 160 additions & 0 deletions src/main/java/com/four/messageconvert/AESUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package com.four.messageconvert;

import org.apache.commons.lang3.StringUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;


/**
* <pre>
* @Description:
*
* </pre>
*
* @version v1.0
* @ClassName: AESUtils
* @Author: sanwu
* @Date: 2021/1/24 11:16
*/
public class AESUtils {

/**
* 加解密密钥, 外部可以
*/
public static final String AES_DATA_SECURITY_KEY = "4%YkW!@g5LGcf9Ut";
/**
* 算法/加密模式/填充方式
*/
private static final String AES_PKCS5P = "AES/ECB/PKCS5Padding";

private static final String AES_PERSON_KEY_SECURITY_KEY = "pisnyMyZYXuCNcRd";

/**
* 加密
*
* @param str 需要加密的字符串
* @param key 密钥
* @return
* @throws Exception
*/
public static String encrypt(String str, String key) {
if (StringUtils.isEmpty(key)) {
throw new RuntimeException("key不能为空");
}
try {
if (str == null) {
return null;
}
// 判断Key是否为16位
if (key.length() != 16) {
return null;
}
byte[] raw = key.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
// "算法/模式/补码方式"
Cipher cipher = Cipher.getInstance(AES_PKCS5P);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(str.getBytes("UTF-8"));
// 此处使用BASE64做转码功能,同时能起到2次加密的作用。
return new BASE64Encoder().encode(encrypted);
} catch (Exception ex) {
return null;
}

}

/**
* 解密
*
* @param str 需要解密的字符串
* @param key 密钥
* @return
*/
public static String decrypt(String str, String key) {
if (StringUtils.isEmpty(key)) {
throw new RuntimeException("key不能为空");
}
try {
if (str == null) {
return null;
}
// 判断Key是否为16位
if (key.length() != 16) {
return null;
}
byte[] raw = key.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance(AES_PKCS5P);
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
// 先用base64解密
byte[] encrypted = new BASE64Decoder().decodeBuffer(str);
try {
byte[] original = cipher.doFinal(encrypted);
String originalString = new String(original, "UTF-8");
return originalString;
} catch (Exception e) {
return null;
}
} catch (Exception ex) {
return null;
}
}

/**
* 加密
*
* @param str 需要加密的字符串
* @return
* @throws Exception
*/
public static String encrypt(String str) {
return encrypt(str, AES_DATA_SECURITY_KEY);
}

/**
* 解密
*
* @param str 需要解密的字符串
* @return
*/
public static String decrypt(String str) {
return decrypt(str, AES_DATA_SECURITY_KEY);
}

/**
* 查询的时候对某些字段解密
*
* @param str
* @return
*/
public static String aesDecrypt(String str) {
if (StringUtils.isBlank(str)) {
return " ";
}
String sql = " AES_DECRYPT(from_base64(" + str + ")," + "'" + AES_DATA_SECURITY_KEY + "')";
return sql;
}

/**
* 对personKey加密
*
* @param personKey
* @return
*/
public static String encryptPersonKey(String personKey) {
return AESUtils.encrypt(personKey, AES_PERSON_KEY_SECURITY_KEY);
}

/**
* 对personKey解密
*
* @param personKey
* @return
*/
public static String decryptPersonKey(String personKey) {
return AESUtils.decrypt(personKey, AES_PERSON_KEY_SECURITY_KEY);
}
}
Loading

0 comments on commit 40e040e

Please sign in to comment.