搭建在线文件管理平台(客户端和服务端)

1,391 阅读5分钟

背景

  1. 团队项目开发时需上传静态资源至OSS时无入口,需要运维手动上传及需要进一步确定文件命名、归属bucket及文件夹等,期间需要替换资源时沟通成本和人力成本则瞬间以倍数增长,而且效率低;
  2. 团队项目开发时使用FTP工具手动上传静态资源至文件服务器,一个账号通用,操作无审核,存在权限泄漏、文件覆盖、误删除等问题;
  3. 其他团队也有类似的问题,其他业务线也没有在线文件管理系统;
  4. 此外文件上传、OSS bucket管理缺乏规范,OSS CDN配置不统一,出现不同活动的网络体验参差不齐。
  5. 产品、测试、前后端等均无多余人力投入基础建设;

目标

  • 流畅的基于图形用户界面的用户体验;
  • 支持快速上传,生成基于当前上传的 资源URL;
  • 支持个人账号的的登录、注册、信息修改;
  • 支持查看个人最近相关的项目和文件;
  • 支持项目的新增、删除、修改、查询;
  • 支持项目成员的新增、删除、查询;
  • 支持文件的新增、替换、查询、查看;

项目计划

  1. 实现一个简易版的文件上传平台,仅提供文件上传(单个文件、多个文件、文件夹)来支持业务开发;
  2. 在2021年下半年实现剩余的功能;

项目设计

97835191-2EE5-4BB4-B6FA-014FEA1BF843.png 284B6374-D78C-41D9-AA1C-8BB2B3212011.png

实现方案选取

  • 由于团队项目的主流技术框架是Vue.js,为了打破技术局限性、拓宽团队技术边界、用多技术栈支撑业务,再结合目前前端主流的成熟的技术框架的开发体验、市场拥抱情况等,在线文件管理平台使用了React.js v17.x + Next.js;
  • 使用在React.js 框架中独占鳌头的Antd UI组件库;
  • 使用经典的状态管理器 Redux;
  • 使用轻便的Less 预处理器;
  • 使用PM2进程管理器;
  • 使用Node.js + Koa.js 2.x 来实现后端服务;
  • 使用noSQL数据库中灵活高性能的MongoDb存储数据;

项目状态

  1. 2021年7月前完成了简易版的文件上传平台,并推广到团队内部、产品团队、兄弟团队使用;
  2. 2022年前完成了剩余功能;

gitlab

疑难问题

browser端

1、antd-img-crop头像裁剪器失效?

答:antd-img-crop包内部通过向upload组件传入异步beforeUpload props等来拦截上传并插入裁剪操作,由于在开发过程中覆盖了beforeUpload props,导致裁剪器失效。

2、如何在Next.js中使用Less预处理器?

答:安装next-with-less,并进行如下配置:

const withLess = require('next-with-less');
module.exports = withLess({
  // other config`
});

3、如何使用模块路径别名

答:在项目指定目录中添加 jsconfig.jsontsconfig.json 并进行相关配置,详见:Absolute Imports and Module path aliases

4、如何抽离封装通用的eslint配置

答:根据eslint plugin开发规则,已抽离封装通用的Next.jsVue.js 2.xeslint配置,详见eslint-plugin-personalized-recommended

5、redux-promise不支持payloadasync 函数

答:redux-promise 目前淡于维护,同时可以看到该包使用率较低,可以尝试使用下redux的比较热门的替代品 mobxdvaimmer等。

6、如何在Nex.js中使用css-in-jsx

答:方案一:内联样式;方案二:引入并配置 styled-jsx

7、如何使用git pre-commit hooks进行eslint检测?

答:请查阅使用 pre-commit Hooks 检查代码

8、函数组件间如何通信?答:

类型父子组件兄弟组件其他组件
props✔️✔️✔️
Context✔️✔️✔️
Ref✔️✔️✔️
共享状态、方法✔️✔️✔️
其他自定义实现的发布-订阅、观察者等设计模式✔️✔️✔️

9、React hooks如何保持执行和调用顺序?

答:通过维护一个组件内部的「记忆单元格」列表,在遵循hook规范的前提下,通过前后移动列表中的指针来切换Hook。

10、使用axios封装的自定义Hooks在更新数据后未更新state

答:自定义Hooks需要配合使用useState等Hook来定义和实现state,而不只是单纯的以use开头的函数命名。

server端

1、如何处理日志?

答:在项目使用pm2时,不可以将日志存放到项目目录中,防止频繁restart;可以存放在数据库或server机器的某个日志文件中;日志格式:client ip — date — 日志内容 — userAgent

2、日期查询无效?

答:BSON Date是一个64位整数,表示自1970/01/01以来的毫秒数,UTC 时间。首先在Schemas定义时将字段定义为Date类型,然后在查询时使用new Date()进行格式化,查询出的日期均是经过toISOString()格式化的。

3、mongoose如何定义数组-对象格式Schemas

答:比较优雅的方式使用嵌套Schema,参考如下:

// 文件信息
file: [
    new mongoose.Schema({
      uid: String,
      // 文件状态:done、removed
      status: String,
      // 文件url
      url: String,
      // 文件名称
      name: String,
      // 文件类型
      type: String
    })
],

4、如何处理注册登录的token

答:比较优雅的方式采用JWT(TODO);目前采用的方式是在保证账户名称唯一前提下,对账户名计算HASH值,将HASH值存储在用户信息表中并在登录时返回给客户端。通过对比每个接口的Authorization请求头与数据库中的Hash值来实现鉴权。

build and deploy

1、clientserver如何识别应用状态来判断是否执行启动命令?答:

#!/bin/bash
# 识别应用是否已启动,未启动时使用pm2 启动,否则无需手动启动。
 
# online-file-management-server 有效进程的数量
processCount=`ps -ef |grep -w  online-file-management-server|grep -v grep|wc -l`
 
# 当进程的数量不小于1时,代表已经该应用已启动,应用更新时会自动重启,否则使用pm2启动应用
if [ $processCount -ge 1 ];then
  echo "online-file-management-server already started!"
else
  npm run pm2:start
  echo "online-file-management-server begin start!"
fi

2、jenkins构建部署时解压缩前有残留内容,导致模块解析异常,由于不确定性,不建议清空文件夹,部署时需要特别注意。

3、执行next build 时会执行next lint,未处理好eslint时会导致构建失败。

runtime

1、接口请求出现了跨域问题?

答:在nginxserver设置响应头,具体配置如下:

// server 允许跨域请求中间件
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Origin', process.env.PROCESS_ENV === PRODUCTION ? 'http://file-oint.61info.cn' : '*')
  ctx.set('Access-Control-Allow-Headers', 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Authorization')
  ctx.set('Access-Control-Allow-Methods', 'PUT, DELETE, GET, POST, OPTIONS')
  ctx.set('Access-Control-Allow-Credentials', true)
  await next();
})
 
// nginx 允许跨域
 add_header Access-Control-Allow-Origin 'http://file-oint.61info.cn';
 add_header Access-Control-Allow-Credentials true;
 add_header Access-Control-Allow-Methods 'PUT, DELETE, GET, POST, OPTIONS';
 add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Authorization';

2、浏览器部分请求偶尔失败,提示connection refused!

答:原因为开发初期将日志文件存放到项目目录中,随着日志文件的变动导致pm2频繁重启。可以设置pm2 ignore或将日志文件放置到项目外部。

3、浏览器控制台警告:[Deprecation] "Authorization" will not be covered by the wildcard symbol (\*)in CORS "Access-Control-Allow-Headers" handling.

答:Access-Control-Allow-Headers设置为*时现在不能包含Authorization字段了,需要单独明确设置。

4、如何获取client ip?

答:将nginx$proxy_add_x_forwarded_for变量重新定义或者添加发往后端服务器的请求头x-forwarded-for,然后在server端通过req.headers["x-forwarded-for"]获取,其中第一个逗号前的IP即为用户真实IP。具体配置如下:

/**
* 获取客户端IP
* @param {Object} req node.js req对象
* @return {string} ip地址
*/
module.exports = (req) => {
  var ipAddress;
  var forwardedIpsStr = req.headers['x-forwarded-for'];
  if (forwardedIpsStr) {
    var forwardedIps = forwardedIpsStr.split(',');
    ipAddress = forwardedIps[0];
  }
  if (!ipAddress) {
    ipAddress = req.connection.remoteAddress;
  }
  return ipAddress;
}