数据表实现随机背包
需求:
- 支持随机生成一个指定长度的数字密码,且不能和正在的使用密码重复。
- 已经使用完毕的密码可以被再次随机取到。
设计
- 参考随机队列算法实现
实现
1. 数据表设计
CREATE TABLE `system_conf_password_data` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`value` int(11) DEFAULT NULL COMMENT '值',
`status` int(11) DEFAULT NULL COMMENT '状态 10:未使用,20:已使用',
PRIMARY KEY (`id`),
KEY `idx_value` (`value`) COMMENT '值的索引'
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COMMENT='系统密码数据表'
2. 代码实现
接口定义:
public interface ISystemConfPasswordDataService {
/**
* 随机获取下一个未使用的密码,如果密码全部取完,返回null。
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
* @return
*/
public Integer updateForGetNextPassword();
/**
* 重置指定密码,将指定密码重新加入密码库。
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
* @param password
*/
public void resetPassword(Integer password);
/**
* 重置所有密码
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
*/
public void resetAll();
}
接口实现:
public class SystemConfPasswordDataServiceImpl implements ISystemConfPasswordDataService {
/**
* 系统密码数据表操作接口
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
*/
@Autowired
SystemConfPasswordDataMapper systemConfPasswordDataMapper;
/**
* 未使用
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
*/
private static final Integer STATUS_UNUSED = 10;
/**
* 已使用
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
*/
private static final Integer STATUS_INUSED = 20;
/**
* 最大id
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
*/
private static final Integer MAX_ID=10000;
/**
* 随机获取下一个未使用的密码,如果密码全部取完,返回null。
* @author 徐明龙 XuMingLong
* @createDate 2018-09-10
* @return
*/
@Override
public synchronized Integer updateForGetNextPassword() {
return updateAndGetNextPassword();
}
/**
* 更新并获取一个随机密码,无密码或获取失败,返回null
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
* @return
*/
private synchronized Integer updateAndGetNextPassword() {
// 获取当前未使用的最大id
int maxId = systemConfPasswordDataMapper.getMaxIdByUnUsed();
// 获取一个1-maxId的随机数
int randomId = new Random().nextInt(maxId)+1;
//获取该随机id对应密码并锁定该记录
SystemConfPasswordData randomData = systemConfPasswordDataMapper.selectByIdForUpdate(randomId);
//获取maxId对应密码并锁定
SystemConfPasswordData maxData = systemConfPasswordDataMapper.selectByIdForUpdate(maxId);
//交换获取到的随机值和maxId的值
Integer maxValue = maxData.getValue();
Integer randomValue = randomData.getValue();
maxData.setValue(randomValue);
randomData.setValue(maxValue);
//设置maxId为已使用
maxData.setStatus(STATUS_INUSED);
//更新maxId的数据
systemConfPasswordDataMapper.updateById(maxData);
//更新随机id的数据
systemConfPasswordDataMapper.updateById(randomData);
return randomValue;
}
/**
* 重置指定密码,将指定密码重新加入密码库。
* @author 徐明龙 XuMingLong
* @createDate 2018-09-10
* @param password
*/
@Override
public synchronized void resetPassword(Integer password) {
updateAndResetPassword(password);
}
/**
* 更新并重置一个会议密码,如果该密码未使用,不做任何处理。
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
* @param password
*/
private synchronized void updateAndResetPassword(Integer password) {
//根据密码获取密码数据
SystemConfPasswordData data = systemConfPasswordDataMapper.selectInUsedByValue(password);
if(data==null) {
return;
}
int currentId= data.getId();
//获取该密码对应的密码数据并锁定该记录
SystemConfPasswordData passwordData = systemConfPasswordDataMapper.selectByIdForUpdate(currentId);
// 获取当前未使用的最大id
int maxId = systemConfPasswordDataMapper.getMaxIdByUnUsed();
//取第一个已使用的记录
maxId = maxId + 1;
if(maxId==currentId) {
//当前就是最大的未用id,则直接更新
passwordData.setStatus(STATUS_UNUSED);
//更新随机id的数据
systemConfPasswordDataMapper.updateById(passwordData);
return;
}
//id不能超过最大id
if(maxId >MAX_ID) {
return;
}
//获取maxId+1对应密码并锁定
SystemConfPasswordData maxData = systemConfPasswordDataMapper.selectByIdForUpdate(maxId);
//交换当前密码和maxId+1的值
Integer maxValue = maxData.getValue();
Integer passwordValue = passwordData.getValue();
maxData.setValue(passwordValue);
passwordData.setValue(maxValue);
//设置maxId+1为未使用
maxData.setStatus(STATUS_UNUSED);
//更新maxId的数据
systemConfPasswordDataMapper.updateById(maxData);
//更新随机id的数据
systemConfPasswordDataMapper.updateById(passwordData);
}
/**
* 重置所有密码
* @author 徐明龙 XuMingLong
* @createDate 2018-09-10
*/
@Override
public void resetAll() {
//初始化10000个
List<SystemConfPasswordData> list = new ArrayList<>(10000);
for(int i=0;i<MAX_ID;i++) {
SystemConfPasswordData data = new SystemConfPasswordData();
data.setId(i+1);
data.setValue(i);
data.setStatus(STATUS_UNUSED);
list.add(data);
}
//批量新增
systemConfPasswordDataMapper.insertPassel(list);
}
}
测试代码:
/**
* 测试单线程1000次能否获取到重复的密码
* @author 徐明龙 XuMingLong
* @createDate 2018-06-28
*/
@Test
@Transactional
public void testNextPassword1000() {
//测试是否会重复获取到密码
Map<Integer,Integer> data = new LinkedHashMap<>();
for(int i=0;i<1000;i++) {
Integer passoword = systemConfPasswordDataService.updateForGetNextPassword();
Integer value = data.get(passoword);
assertThat(value).isNull();
data.put(passoword, passoword);
}
data.forEach((key,value)->{
System.out.println("获取到的密码:"+value);
});
}
3. 使用
获取密码:
Integer passoword = systemConfPasswordDataService.updateForGetNextPassword();
使用完毕恢复:
systemConfPasswordDataService.resetPassword(value);