背景
- 团队项目开发时需上传静态资源至OSS时无入口,需要运维手动上传及需要进一步确定文件命名、归属bucket及文件夹等,期间需要替换资源时沟通成本和人力成本则瞬间以倍数增长,而且效率低;
- 团队项目开发时使用FTP工具手动上传静态资源至文件服务器,一个账号通用,操作无审核,存在权限泄漏、文件覆盖、误删除等问题;
- 其他团队也有类似的问题,其他业务线也没有在线文件管理系统;
- 此外文件上传、OSS bucket管理缺乏规范,OSS CDN配置不统一,出现不同活动的网络体验参差不齐。
- 产品、测试、前后端等均无多余人力投入基础建设;
目标
- 流畅的基于图形用户界面的用户体验;
- 支持快速上传,生成基于当前上传的 资源URL;
- 支持个人账号的的登录、注册、信息修改;
- 支持查看个人最近相关的项目和文件;
- 支持项目的新增、删除、修改、查询;
- 支持项目成员的新增、删除、查询;
- 支持文件的新增、替换、查询、查看;
项目计划
- 实现一个简易版的文件上传平台,仅提供文件上传(单个文件、多个文件、文件夹)来支持业务开发;
- 在2021年下半年实现剩余的功能;
项目设计
实现方案选取
- 由于团队项目的主流技术框架是Vue.js,为了打破技术局限性、拓宽团队技术边界、用多技术栈支撑业务,再结合目前前端主流的成熟的技术框架的开发体验、市场拥抱情况等,在线文件管理平台使用了React.js v17.x + Next.js;
- 使用在React.js 框架中独占鳌头的Antd UI组件库;
- 使用经典的状态管理器 Redux;
- 使用轻便的Less 预处理器;
- 使用PM2进程管理器;
- 使用Node.js + Koa.js 2.x 来实现后端服务;
- 使用noSQL数据库中灵活高性能的MongoDb存储数据;
项目状态
- 2021年7月前完成了简易版的文件上传平台,并推广到团队内部、产品团队、兄弟团队使用;
- 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.json 或 tsconfig.json 并进行相关配置,详见:Absolute Imports and Module path aliases。
4、如何抽离封装通用的eslint配置?
答:根据eslint plugin开发规则,已抽离封装通用的Next.js、Vue.js 2.x的eslint配置,详见eslint-plugin-personalized-recommended。
5、redux-promise不支持payload是async 函数?
答:redux-promise 目前淡于维护,同时可以看到该包使用率较低,可以尝试使用下redux的比较热门的替代品 mobx、dva、immer等。
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、client、server如何识别应用状态来判断是否执行启动命令?答:
#!/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、接口请求出现了跨域问题?
答:在nginx或server设置响应头,具体配置如下:
// 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;
}