博客编写云同步

尽管先前已经给博客编写功能添加了导入导出功能,以防备断网时候博客编排内容无法提交的情况产生,还有一个问题需要解决——如何避免编写过程中因手滑页面退出导致的编写内容丢失。

这一块其实很容易就能想到采用云同步来实现。基本原理就是用户在前端富文本编辑器编辑,触发编辑相关事件后,就向服务器发送最新的编辑内容以更新。服务器端则可以将数据暂存在redis服务器中。用户需要同步时,再读取数据即可。

后端编写

首先是控制器,一个保存接口一个读取接口。在保存前堆数据内容可以进行一个简单的校验。

代码语言:javascript
复制
@PostMapping("/cloud/save")
public RetResult<String> saveToCloud(String raw){
    if (raw == null || "".equals(raw.trim())){
        return RetResult.fail("数据为空,未同步");
    }
    return blogService.saveToCloud(raw);
}

@GetMapping("/cloud/load")
public RetResult<String> loadFromCloud(){
return blogService.loadFromCloud();
}

接着是service的实现。其实就是非常简单的redis存储与读取。不过需要注意的是要提前预测 RuntimeException 的产生,以及时的反馈给前端当前同步的状态。

代码语言:javascript
复制
@Override
public RetResult<String> saveToCloud(String raw) {
    ValueOperations<String, String> ops = redisTemplate.opsForValue();
    try{
        ops.set(CLOUD_BLOG_SAVE, raw);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = sdf.format(Calendar.getInstance().getTime());
        return RetResult.success("同步成功", time);
    } catch (Exception e) {
        return RetResult.fail("同步失败!" + e.getMessage());
    }
}

@Override
public RetResult<String> loadFromCloud() {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
try{
String res = ops.get(CLOUD_BLOG_SAVE);
if (res == null || "".equals(res.trim())) return RetResult.fail("无数据内容!");
return RetResult.success("拉取数据成功", res);
} catch (Exception e) {
return RetResult.fail("拉取最新数据失败!" + e.getMessage());
}
}

前端设计

相对来说,前端逻辑实现需要注意的点要多一些。

首先需要考虑的就是同步的频率选择,起初设想的是用定时器来定时发送请求。这种情况存在两种问题,其一,如果编写博客过程中离开,定时器的执行则是无意义的,因为内容一直没有变。其二,如果定时器的时间设置的太短,请求发送太频繁,占用网络资源,设置的太长的话,有时候才思文涌,一分钟几百字出来,在定时器执行前——吧嗒,隔壁小孩调皮的按了你电脑的Ctrl+F4,这时候正当你愤怒的想要呵斥时,看到了她水灵灵的眼镜瞅着你,嘟着小嘴,你是忍气吞声呢还是忍气吞声呢?

既然定时器不行,那么还有一种策略就是事件触发。可以参考腾讯文档,它的保存时机是每次有内容变更时就及时保存下来,当没有变动的时候也不会更新同步。这种触发式的同步策略就很有弹性,因此我采用的就是事件触发的方式存储的。

一般来说,一款经典的富文本编辑器都有编辑事件监听器,我使用的编辑器是wangeditor,在官网中有介绍可以采用 editor.config.onchange 设置编辑监听事件。

代码语言:javascript
复制
export default {
  data () {
    return {
      saveCloud: {
        status: 0, 
        describe: '未同步',
        time: '',
        step: 0
      }
    }
  },
  mounted () {
      // cloud save
      this.editor.config.onchange = this.saveToCloud
      this.editor.config.onchangeTimeout = 500
  },
  methods: {
    saveToCloud () {
      if (this.saveCloud.step < 10) {
        this.saveCloud.step++
        return
      }
      axios.post('/api/blog/cloud/save', 'raw=' + this.editor.txt.html()).then(res => {
        this.saveCloud.status = res.data.code
        this.saveCloud.describe = res.data.msg
        this.saveCloud.time = res.data.data
        this.saveCloud.step = 0
      }).catch(err => {
        console.log(err)
        this.saveCloud.status = -500
        this.saveCloud.describe = '访问服务器异常'
        this.saveCloud.step = 0
      })
    },
    loadFromCloud () {
      axios.get('/api/blog/cloud/load').then(res => {
        if (res.data.code === 200) {
          this.editor.txt.html(res.data.data)
        } else {
          alert(res.data.msg)
        }
      }).catch(err => {
        console.log(err)
        alert('连接服务器异常,请F12查看console')
      })
    }  },
  beforeRouteLeave (to, from, next) {
    this.saveCloud.step = 100
    this.saveToCloud()
    next()
  }}

比较关键的两个设计是step和beforeRouteLeave。

在数据上云时,固然可以使用和腾讯文档一样只要变更就上传,但是这需要增量上传才不会浪费网络资源,不过如何增量是个非常复杂的问题,需要后续深入研究各种情况(目前的想到的方式有两种,一种是类似git存储原理,一种是按照编辑事件增量,类似于redis的aof)。我这边则是折中了一下,就是当编辑事件触发了十次才推送一次更新,即step累计到10才执行,请求相应结束后值0。

至于beforeRouteLeave的设计是在有step的基础上才有必要存在的,因为有了10次step,有时候页面切换前,并没有达到step,这时候就需要在路由跳转前强行推送一次了。目前10次step存在一个明显的问题,就是浏览器异常退出且有更新未保存的时候,新增的几步编辑会丢失,不过影响应该不是很大吧()

以下就是结果图啦????