前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >博客——使用 Redis 实现博客编辑的自动保存草稿功能

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

作者头像
凡人飞
发布2020-09-21 11:11:57
1.4K0
发布2020-09-21 11:11:57
举报
文章被收录于专栏:指缝阳光指缝阳光

一、功能需求

介绍:

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

二、Springboot 中 Redis 设置

  1. 首先我们 Springboot 项目需要集成 Redis,具体集成方法我就不详述了(网上搜很多)。下面贴出我的 Redis 的序列化配置:
代码语言:javascript
复制
@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;
}
  1. 因为我们存储的是文章信息,所以肯定是一个对象,由此使用 Redis 的 Hash 类型来存储。我们使用 RedisTemplate 来操作,以下代码为对 Hash 类型数据进行操作的工具类 RedisUtil
代码语言:javascript
复制
/**
 * 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);
}
  1. 对于 Redis 的业务操作,我提取出了 RedisService 。此处的操作主要是文章类的新增、获取和删除操作。提取出的方法如下:

RedisService 接口:

代码语言:javascript
复制
/**
 * 保存文章
 *
 * @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 实现类(因为文章参数类继承了文章类,因此反射获取属性的时候需要获取父类属性):

代码语言:javascript
复制
@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 和文章实体就可以进行草稿保存了。
代码语言:javascript
复制
/**
 * 自动保存,编辑文章时每隔 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 的生成使用的格式如下:

代码语言:javascript
复制
/**
 * 文章自动保存时存储在 Redis 中的 key ,后面 {0} 是用户 ID
 */
String AUTO_SAVE_ARTICLE = "auto_save_article::{0}";
  1. 获取文章的实现此时就比较简单了,如下:
代码语言:javascript
复制
/**
 * 从 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);
} 
  1. 最后就是删除草稿,当我们成功提交文章后,就调用删除方法,对草稿进行删除,此处只贴出了具体的删除代码。
代码语言:javascript
复制
// 文章新增或修改成功,则将当前用户在 Redis 中的草稿进行删除
// 生成存储的 key
String key = MessageFormat.format(AUTO_SAVE_ARTICLE, currentUser.getId());
redisService.deleteArticle(key);
log.info("删除草稿文章 key=" + key + " 成功!");

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

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

五、总结

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、功能需求
    • 二、Springboot 中 Redis 设置
      • 三、使用 RedisService 实现草稿功能
        • 四、前端对自动保存接口进行调用
          • 五、总结
          相关产品与服务
          云数据库 Redis
          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档