数据表实现随机背包

需求:

  1. 支持随机生成一个指定长度的数字密码,且不能和正在的使用密码重复。
  2. 已经使用完毕的密码可以被再次随机取到。

设计

  1. 参考随机队列算法实现

实现

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);