喝水小助手实现思路附代码案例



大家好,我是本群的提醒喝水小助手,这是今天的第X轮。希望此刻看到消息的人可以和我一起来一杯水。一小时后的我继续提醒大家喝水。和我一起成为一天八杯水的人吧!

最初是为了提醒大家注意身体健康,也有提醒运动、提醒站立等等,后来大家脑洞大开的创作出了各种滑稽搞笑的表情包,例如提醒喝可乐,提醒上厕所等等,该表情包也开始在不同的圈子里流行起来,例如在游戏里有提醒开黑,在B站有提醒更新视频等等。

定时任务

利用SpringTask、Quartz实现定时提醒发送,不过小程序的消息模板推送已经不支持随意给用户发消息了,需要用户自行去触发。

延迟队列

用户手动触发一次喝水动作,这时候我们可以使用延迟队列来实现,推荐使用开源的第三方工具包redisson,同时保证redis支持lua特性。

配置application.properties

# redisson lock
redisson.address=redis://127.0.0.1:6379
redisson.password=123456

参数配置类RedissonProperties

@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {

    private int timeout = 3000;

    private String address;

    private String password;

    private int connectionPoolSize = 64;

    private int connectionMinimumIdleSize=10;

    private int slaveConnectionPoolSize = 250;

    private int masterConnectionPoolSize = 250;

    private String[] sentinelAddresses;

    private String masterName;

}

自动装配 RedissonAutoConfiguration

@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {

    @Autowired
    private RedissonProperties redssionProperties;

    /**
     * 单机模式自动装配
     * @return
     */
    @Bean
    RedissonClient redissonSingle() {
        Config config = new Config();
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(redssionProperties.getAddress)
                .setTimeout(redssionProperties.getTimeout())
                .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
        if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
            serverConfig.setPassword(redssionProperties.getPassword());
        }

        return Redisson.create(config);
    }

    /**
     * 装配locker类,并将实例注入到RedissLockUtil中
     * @return
     */
    @Bean
    RedissLockUtil redissLockUtil(RedissonClient redissonClient) {
        RedissLockUtil redissLockUtil = new RedissLockUtil();
        redissLockUtil.setRedissonClient(redissonClient);
        return redissLockUtil;
    }
}

工具类RedissLockUtil

/**
 * redis分布式锁帮助类
 * @author 爪哇笔记 By https://blog.52itstyle.vip
 */
public class RedissLockUtil {

    private static RedissonClient redissonClient;

    public void setRedissonClient(RedissonClient locker) {
        redissonClient = locker;
    }

    /**
     * 初始红包数量
     * @param key
     * @param count
     */
    public void initCount(String key,int count) {
        RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
        mapCache.putIfAbsent(key,count,3,TimeUnit.DAYS);
    }
    /**
     * 递增
     * @param key
     * @param delta 要增加几(大于0)
     * @return
     */
    public int incr(String key, int delta) {
        RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return  mapCache.addAndGet(key, 1);//加1并获取计算后的值
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public int decr(String key, int delta) {
        RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return mapCache.addAndGet(key, -delta);//加1并获取计算后的值
    }
    public static RedissonClient getRedissonClient() {
        return redissonClient;
    }
}

入队列,伪代码:

RedissonClient redissonClient = RedissLockUtil.getRedissonClient();
/**
* 目标队列
*/
RBlockingQueue<RemindDelay> blockingRemindQueue
= redissonClient.getBlockingQueue("remindDelayQueue");
/**
* 定时任务将到期的元素转移到目标队列
*/
RDelayedQueue<RemindDelay> delayedRemindQueue
= redissonClient.getDelayedQueue(blockingRemindQueue);
/**
* 延时信息入队列
*/
delayedRemindQueue.offer(new RemindDelay(remind.getId()), sec, TimeUnit.SECONDS);

队列消息实体标识:

public class RemindDelay implements Serializable {

    /**
     * 主键ID
     */
    private  long id;

    /**
     * 创建时间戳
     */
    private  long timestamp;

    public RemindDelay() {

    }

    public RemindDelay(long id) {
        this.id = id;
        this.timestamp = System.currentTimeMillis();
    }

    public long getId() {
        return id;
    }

    public long getTimestamp() {
        return timestamp;
    }
}

消费提醒队列:

/**
 * 消费提醒队列
 */
@Component
public class RemindRunner implements ApplicationRunner {

    private final static Logger LOGGER = LoggerFactory.getLogger(RemindRunner.class);

    private static int corePoolSize = Runtime.getRuntime().availableProcessors();
    private static ThreadPoolExecutor executor  =
            new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10L, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(100));

    @Autowired
    private RemindService remindService;

    @Autowired
    private WxMaService wxService;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        /**
         * 出队列
         */
        RedissonClient redissonClient = RedissLockUtil.getRedissonClient();
        RBlockingQueue<RemindDelay> delayedRemindQueue
                = redissonClient.getBlockingQueue("remindDelayQueue");
        new Thread(() -> {
            LOGGER.info("提醒队列启动成功");
            while (true){
                try {
                    RemindDelay delay = delayedRemindQueue.take();
                    push(delay);
                    LOGGER.info("提醒ID:{}",delay.getId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }
    public void push(RemindDelay delay){
        /**
         * 推送
         */
        Runnable task = () -> {
            //发送消息通知逻辑
        };
        executor.execute(task);
    }
}

适用场景

淘宝订单到期,下单成功后60s之后给用户发送短信通知,限时支付、缓存系统、红包过期、开奖提醒等等。

小结

以上方案单机模式,生产环境可根据实际业务需求选择使用。

爪哇笔记

作者: 小柒

出处: https://blog.52itstyle.vip

分享是快乐的,也见证了个人成长历程,文章大多都是工作经验总结以及平时学习积累,基于自身认知不足之处在所难免,也请大家指正,共同进步。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 如有问题, 可邮件(345849402@qq.com)咨询。