builder icon indicating copy to clipboard operation
builder copied to clipboard

Load & Save the project implementation

Open palp1tate opened this issue 1 year ago • 1 comments

背景

项目背景

在当前的软件开发实践中,项目管理和文件版本控制是核心组成部分。项目涉及多种文件类型,包括代码、图片、音频等,需求对这些文件进行有效管理以支持项目的开发和维护。

需求背景

用户需要一个方便、高效的方式来创建、编辑、保存和管理项目文件。特别是在进行文件编辑时,能够实现对文件的增量更新,而不仅仅是全量更新,以提高效率和节省资源。同时,项目文件的唯一性和不重复性需要得到保证。

方案概述

项目文件的存储不再以项目为单位存储到对象存储,而是以文件为单位进行存储

角色及其职责

  • 前端角色:负责用户界面的设计和实现,包括项目文件的创建、编辑和显示。前端还需要处理与后端的通信,以及直接与对象存储服务进行交互来上传和删除文件。
  • 后端角色:负责处理来自前端的请求,包括提供项目的完整信息和更新项目信息。项目信息主要包括项目的文件结构、版本号、iduid、更新时间等。后端不直接与对象存储打交道。
  • 对象存储服务:提供项目文件的存储能力,每个文件通过哈希算法生成的唯一URL进行访问,保证文件的唯一性和不重复性。

如何串起来

  1. 项目加载:前端向后端发起HTTP请求,后端从数据库中获取项目的完整信息并返回给前端,前端解析并显示项目内容。

    其中文件结构的示例如下(数据库使用MySQL,不能存储数组,这里会将 JSON 序列化(marshal)为字符串就可以方便的将项目的结构存储到数据库中)

   {
	"index.json": "https://xxx.com/d2a6802666d0d9248393d0b02abc93ae.json",
	"assets/demo.png":"https://xxx.com/f535a73a3dfe15ca7c623512f09d5ed4.png"
   }

键对应该文件在项目中的相对路径,值对应该文件的线上地址(使用对象存储)。后端需要将项目文件结构做一次反序列化(unmarshal)再给前端,进而实现加载整个项目。

前端加载完整个项目后,浏览器里可能会有类似下面的数据结构:

const projectFiles = {
  "index.json": {
    onlineUrl: "https://xxx.com/123.json",
    content: '...'
  }, 
  // ...
}

键对应文件的相对路径,值包含了线上地址,文件内容等等。

  1. 项目编辑和保存:用户编辑项目文件后,前端直接与对象存储服务交互,实现文件的上传和删除。编辑完成后,前端将更新后的项目文件结构发送给后端,后端更新数据库。

核心逻辑细节说明

加载项目文件

  • 前端请求:携带项目ID向后端发起项目加载请求。
  • 后端处理:根据项目ID查询数据库中该项目对应的完整信息并返回给前端。

保存编辑过的项目

  • 文件处理:用户编辑文件后,前端删除该文件的onlineUrl,并与对象存储服务交互,删除旧文件,上传新文件,获取新的url
  • 项目文件结构更新:前端收集所有文件的新url,形成更新后的项目文件结构。
  • 后端更新数据库:前端发送更新后的项目文件结构给后端,后端将其序列化后更新到数据库中,同时版本号做相应改变。

需要注意的点

  • 采用对某个文件使用哈希算法(比如md5算法)得到的值作为该文件的文件名,后缀名当然保持不变。这样有一个好处,这个文件的线上地址能够唯一标识这个文件,也不会存在文件重复的情况,节省了资源,因为重复文件的url必定是一样的。同时如果一个文件有变动,那么它的url必定改变,这也为我们实现一种客户端与服务端之间文件增量更新的变种方案提供了支撑。

  • 用户可能会有下载整个项目工程文件的需求,这个时候就可以直接用七牛的这个功能,不需要自己去做打包,也就是说前端直接和对象存储打交道,而不需要走后端便可以快速的打包整个项目文件。

  • 用户在UI界面点击编辑按钮后,会在前端保存的数据结构中删除该文件的线上地址,也就是onlineUrl,删除线上地址后前端还可以同时也把对象存储中的对应文件也删了,为什么要这样做呢,因为一旦编辑过该文件,我们即可认为该文件已经有变动,而而变动后的文件是没有新的线上地址的,所以我们对编辑过后的文件删除旧的线上地址,在后面用户点击保存整个项目后,前端就能够通过该文件结构进行文件的变相增量更新。具体是什么原理呢,一个很重要的点就是,未编辑变动的文件它的线上地址还在,在数据库中也完好保存,变动的文件它的线上地址不存在,数据库中保存的也是旧文件的线上地址,这样的话,前端就能够不经过后端而直接与对象存储打交道,根据浏览器中的每个项目文件的线上地址是否存在,从而实现变更文件的更新上传,按这个想法,其实是可以看作一种增量更新的。

    前端更新项目可能的逻辑:

    function saveProject() {
     var fileTree = {}
     for (const path of projectFiles) {
      var url = projectFiles[path].onlineUrl
      if (url == null) {
       url = uploadFile(path, projectFiles[path].content)
      }
      fileTree[path] = url
     }
     postToServer(fileTree)
    }
    
  • 用户如果是第一次新建项目的话,自然用户编辑的每个文件都没有线上地址,如果要保存项目的话就可以像上面的方式依次上传没有url的文件,然后将新的文件结构传给后端。

  • 约定项目的初始版本为1,用户点击保存项目后,后端在更新数据库的时候将版本号加一。

  • 整个项目文件的加载,保存的过程中,前端和后端交互只涉及加载项目时后端返回项目的文件结构和保存项目时后端修改数据库表中的文件结构,后端不与对象存储打交道,前端直接与对象存储打交道。

palp1tate avatar Feb 07 '24 06:02 palp1tate