博客——使用 Redis 实现博客编辑的自动保存草稿功能

本人个人博客网站,欢迎访问:学教府

一、功能需求

介绍:

  1. 在做个人博客网站时。在我们编辑博客时,有可能会突然关闭浏览器或浏览器崩溃的情况,而此时我们的文章才写一半,还没进行保存。如果没有自动保存功能,则此时只能惟有泪千行了。因此需要一个自动保存文章为草稿的功能。
  2. 我在此处实现该功能的思路:在前端每隔 3 分钟调用一次自动保存草稿的接口,数据暂存在 Redis 数据库中(有效期设置为 1 天)。这样当我们意外关闭了页面,下次该用户写博客时会加载出之前草稿。

二、Springboot 中 Redis 设置

  1. 首先我们 Springboot 项目需要集成 Redis,具体集成方法我就不详述了(网上搜很多)。下面贴出我的 Redis 的序列化配置:
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    
        // 配置连接工厂
        redisTemplate.setConnectionFactory(factory);
    
        // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值(默认使用 JDK 的序列化方式)
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY 是都有包括 private 和 public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非 final 修饰的,final修饰的类,比如 String,Integer 等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 解决jackson2无法反序列化LocalDateTime的问题
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(new JavaTimeModule());
    
        jackson2JsonRedisSerializer.setObjectMapper(om);
    
        // 使用 StringRedisSerializer 来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 值采用 json 序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    
        // 设置 hash 的 key 和 value 序列化模式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
    
        return redisTemplate;
    }
    
  2. 因为我们存储的是文章信息,所以肯定是一个对象,由此使用 Redis 的 Hash 类型来存储。我们使用 RedisTemplate 来操作,以下代码为对 Hash 类型数据进行操作的工具类 RedisUtil
    /**
     * Hash 存储 map 实现多个键值保存并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String,Object> map, long time){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }
    
  3. 对于 Redis 的业务操作,我提取出了 RedisService 。此处的操作主要是文章类的新增、获取和删除操作。提取出的方法如下:
    RedisService 接口:
    /**
     * 保存文章
     *
     * @param key
     * @param article 文章
     * @param expireTime 过期时间
     * @return
     */
    boolean saveArticle(String key, ArticlePublishParam article, long expireTime);
    
    /**
     * 获取文章
     *
     * @param key
     * @return
     */
    ArticlePublishParam getArticle(String key);
    
    /**
     * 删除文章
     *
     * @param key
     */
    void deleteArticle(String key);
    
    RedisServiceImpl 实现类(因为文章参数类继承了文章类,因此反射获取属性的时候需要获取父类属性):
    @Override
    public boolean saveArticle(String key, ArticlePublishParam articlePublishParam, long expireTime) {
    
        // 1. 首先将文章转为 map
        BeanMap beanMap = BeanMap.create(articlePublishParam);
    
        // 2. 保存到 redis
        return redisUtil.hmset(key, beanMap, expireTime);
    }
    
    @Override
    public ArticlePublishParam getArticle(String key) {
        Map<Object, Object> map = redisUtil.hmget(key);
    
        if (CollectionUtils.isEmpty(map)){
            return null;
        }else {
            return JSON.parseObject(JSON.toJSONString(map), ArticlePublishParam.class);
        }
    }
    
    @Override
    public void deleteArticle(String key) {
        // 1. 首先获取 Article 类的所有字段名称
        List<String> fieldNameList = getFieldNameList(ArticlePublishParam.class);
    
        // 2. 删除对应的对象 hash
        redisUtil.hdel(key, fieldNameList.toArray());
    }
    
    /**
     * 获取一个类的所有字段名称
     * @param clazz
     * @return
     */
    private List<String> getFieldNameList(Class clazz) {
        List<String> fieldNameList = new ArrayList<>();
    
        // 1. 获取本类字段
        Field[] filed = clazz.getDeclaredFields();
        for(Field fd : filed) {
            String filedName = fd.getName();
            // 将序列化的属性排除
            if (!"serialVersionUID".equals(filedName)) {
                fieldNameList.add(filedName);
            }
        }
    
        // 2. 获取父类字段
        Class<?> superClazz = clazz.getSuperclass();
        if (superClazz != null) {
            Field[] superFields = superClazz.getDeclaredFields();
            for (Field superField : superFields) {
                String filedName = superField.getName();
                // 将序列化的属性排除
                if (!"serialVersionUID".equals(filedName)) {
                    fieldNameList.add(filedName);
                }
            }
        }
    
        return fieldNameList;
    }
    

三、使用 RedisService 实现草稿功能

  1. 此时我们只需要根据业务生成对应的 key 和文章实体就可以进行草稿保存了。
    /**
     * 自动保存,编辑文章时每隔 3 分钟自动将数据保存到 Redis 中(以防数据丢失)
     *
     * @param param
     * @param principal
     * @return
     */
    @PostMapping("/autoSave")
    public ReturnResult autoSave(@RequestBody ArticlePublishParam param, Principal principal) {
        if (Objects.isNull(param)) {
            return ReturnResult.error("参数错误");
        }
        if (Objects.isNull(principal)) {
            return ReturnResult.error("当前用户未登录");
        }
    
        // 1. 获取当前用户 ID
        User currentUser = userService.findUserByUsername(principal.getName());
    
        // 2. 生成存储的 key
        String key = MessageFormat.format(AUTO_SAVE_ARTICLE, currentUser.getId());
    
        // 3. 保存到 Redis 中, 过期时间为 1 天。此处是文章的参数类 ArticlePublishParam
        boolean flag = redisService.saveArticle(key, param, 24L * 60 * 60 * 1000);
        if (flag) {
            log.info("保存 key=" + key + " 的编辑内容文章到 Redis 中成功!");
            return ReturnResult.success();
        } else {
            return ReturnResult.error("自动保存文章失败");
        }
    }
    
    其中 key 的生成使用的格式如下:
    /**
     * 文章自动保存时存储在 Redis 中的 key ,后面 {0} 是用户 ID
     */
    String AUTO_SAVE_ARTICLE = "auto_save_article::{0}";
    
  2. 获取文章的实现此时就比较简单了,如下:
    /**
     * 从 Redis 中获取当前登录用户的草稿文章
     *
     * @param principal
     * @return
     */
    @GetMapping("/getAutoSaveArticle")
    public ReturnResult getAutoSaveArticle(Principal principal) {
        if (Objects.isNull(principal)) {
            return ReturnResult.error("当前用户未登录");
        }
    
        // 1. 获取当前用户 ID
        User currentUser = userService.findUserByUsername(principal.getName());
    
        // 2. 生成存储的 key
        String key = MessageFormat.format(AUTO_SAVE_ARTICLE, currentUser.getId());
    
        // 3. 获取文章信息
        ArticlePublishParam article = redisService.getArticle(key);
    
        if (article != null && StringUtils.isNotBlank(article.getTagsStr())){
            String[] split = article.getTagsStr().split(",");
            article.setTagStringList(Arrays.asList(split));
        }
    
        log.info("获取草稿文章 key=" + key + " 的内容为:" + article);
        return ReturnResult.success(article);
    }
    
  3. 最后就是删除草稿,当我们成功提交文章后,就调用删除方法,对草稿进行删除,此处只贴出了具体的删除代码。
    // 文章新增或修改成功,则将当前用户在 Redis 中的草稿进行删除
    // 生成存储的 key
    String key = MessageFormat.format(AUTO_SAVE_ARTICLE, currentUser.getId());
    redisService.deleteArticle(key);
    log.info("删除草稿文章 key=" + key + " 成功!");
    

四、前端对自动保存接口进行调用

  1. 此时后台接口已经准备好,我们需要做的就是前台每隔 3 分钟调用一次保存方法。我们也可以自己加一个手动保存的按钮。
    // 每隔 3 分钟自动将数据存入草稿中,没提交时以防数据丢失, saveDraft() 是一个 ajax 方法
    setInterval(function () { saveDraft() }, 3 * 60 * 1000);
    

五、总结

归纳: 到此,自动保存草稿的核心已经介绍完了。实现还是比较简单,同时也有其他的方法,比如使用 localStorage 等方法也可以实现。关键点就是在一个地方暂存文章。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值