项目背景

随着业务的不断发展,研发链路的效能提升也是一个至关重要的指标,其中对前端工程基建而言,其上游部分主要是和设计师同学打交道,而在整个研发链路中,通常会有设计走查的流程来让设计师同学辅助测试同学完成UI测试。设计师在进行走查的过程中,肉眼的比对偶尔会忽略一些细微部分,同时也会耗费设计师大量的精力,为了辅助设计同学能够更高效的进行设计走查,本文旨在通过设计走查平台在后端侧的实践总结下对于视觉稿还原程度比对的一些思路。

方案

后端架构选型,对于前端基建部分的后端应用而言,通常是选择node.js来进行处理,虽然走查平台后端涉及到了图片的对比计算,但是在集团层面提供了各种云服务,因而可以利用云服务相关的各种中间件来保证前端基建后端服务的高可用与高性能。设计走查平台涉及到了图片上传后的临时存储,如果使用云存储,比如:对象存储等,势必涉及到大量的与云平台的交互,较为繁琐,而本应用业务主要是用于处理两张图片的比对,计算要求要高于存储要求,因而选择临时文件存储在本地系统中,但这就带来了一个问题,那就是大量对比需求可能会将服务搞崩,考虑到node.js服务的多进程单线程的特性,我们这里引入了pm2来进行进程管理,同时使用定时任务cron对临时文件进行定时清理(ps:这里也可以利用k8s的定时任务进行处理),保证业务的可用性。

目录

  • db

    • temp
  • server
    • routes

      • piper

        • compare
        • upload
        • index.js
    • app.js
  • ecosystem.config.js

实践

对图片比对部分,这里使用 looks-same 库来进行png图片的比对,其本质是通过(x,y)像素的差异比对进行pixel图片的覆盖描绘,最后输出一个对比叠加的图片,其他的库还有 pixel-match 以及 image-diff 等都可以来进行图片的比对

源码

piper

upload

用于图片的上传,使用 multermultipart/form-data 进行转换

const router = require('../../router');
const multer = require('multer');
const path = require('path');
const fs = require('fs');const storage = multer.diskStorage({destination: function(req, file, cb) {if(file.mimetype == 'image/png') {cb(null, path.resolve(__dirname, '../../../../db/__temp__'))} else {cb({ error: 'Mime type not supported' })}},filename: function(req, file, cb) {cb(null, `${Date.now()}.${file.originalname}`)}
})/*** @openapi* /piper/upload/putImage:post:summary: 上传图片tags: - putImagerequestBody:required: truecontent: application/json: schema: $ref: '#/components/schemas/putImage'responses:  '200':content:application/json:example:code: "0"data: ""msg: "成功"success: true*/
router.post('/putImage', multer({storage: storage
}).single('img'), async function (req, res) {console.log('putImage', req.file);// 定时删除上传的图片setTimeout(() => {if(fs.existsSync(path.resolve(__dirname, `../../../../db/__temp__/${req.file.filename}`))) {fs.unlink(path.resolve(__dirname, `../../../../db/__temp__/${req.file.filename}`), function (err) {if (err) {console.error(`删除文件 ${req.file.filename} 失败,失败原因:${err}`)}console.log(`删除文件 ${req.file.filename} 成功`)});} else {console.log(`文件 ${req.file.filename} 不存在`)}}, 120 * 1000)return res.json({code: "0",data: {filename: req.file.filename,size: req.file.size},msg: '成功',success: true})
});module.exports = router;

compare

使用 looks-same 对图片进行比对,点击下载后可以获取对比的图片

const router = require('../../router');
const looksSame = require('looks-same');
const path = require('path');
const fs = require('fs');/*** @openapi* /piper/compare/compareImage:post:summary: 比较图片tags: - compareImagerequestBody:required: truecontent: application/json: schema: $ref: '#/components/schemas/compareImage'responses:  '200':content:application/json:example:code: "0"data: ""msg: "成功"success: true*/
router.post('/compareImage', function (req, res) {console.log('compareImage', req.body);const {designName,codeName} = req.body;if (designName && codeName) {if (fs.existsSync(path.resolve(__dirname, `../../../../db/__temp__/${designName}`)) && fs.existsSync(path.resolve(__dirname, `../../../../db/__temp__/${codeName}`))) {const [, ...d] = designName.split('.'),[, ...c] = codeName.split('.');d.pop();c.pop();const dName = d.join(''),cName = c.join('');const compareName = `${Date.now()}.${dName}.${cName}.png`;looksSame.createDiff({reference: path.resolve(__dirname, `../../../../db/__temp__/${designName}`),current: path.resolve(__dirname, `../../../../db/__temp__/${codeName}`),diff: path.resolve(__dirname, `../../../../db/__temp__/${compareName}`),highlightColor: '#ff00ff', // color to highlight the differencesstrict: false, // strict comparsiontolerance: 2.5,antialiasingTolerance: 0,ignoreAntialiasing: true, // ignore antialising by defaultignoreCaret: true // ignore caret by default}, function (error) {if (error) {return res.json({code: "-1",data: error,msg: '失败',success: false})} else {[codeName, designName].forEach(item => {fs.unlink(path.resolve(__dirname, `../../../../db/__temp__/${item}`), function (err) {if (err) {console.error(`删除文件 ${item} 失败,失败原因:${err}`)}console.log(`删除文件 ${item} 成功`)});})return res.json({code: "0",data: {compareName: compareName},msg: '成功',success: true})}});} else {return res.json({code: "-1",data: '所需对比图片不存在,请重新确认',msg: '失败',success: false})}} else {return res.json({code: "-1",data: '所需对比图片无法找到,请重新确认',msg: '失败',success: false})}
});/*** @openapi* /piper/compare/downloadImage:post:summary: 下载比较图片tags: - downloadImagerequestBody:required: truecontent: application/json: schema: $ref: '#/components/schemas/downloadImage'responses:  '200':content:application/json:example:code: "0"data: ""msg: "成功"success: true*/
router.post('/downloadImage', function (req, res) {console.log('downloadImage', req.body);const {compareName} = req.body;if (compareName) {const f = fs.createReadStream(path.resolve(__dirname, `../../../../db/__temp__/${compareName}`));res.writeHead(200, {'Content-Type': 'application/force-download','Content-Disposition': 'attachment; filename=' + compareName});   f.on('data', (data) => {res.write(data);}).on('end', () => {res.end();fs.unlink(path.resolve(__dirname, `../../../../db/__temp__/${compareName}`), function (err) {if (err) {console.error(`删除文件 ${compareName} 失败,失败原因:${err}`)}console.log(`删除文件 ${compareName} 成功`)});})} else {return res.json({code: "-1",data: '生成对比图片名称不正确,请重新确认',msg: '失败',success: false})}
});module.exports = router;

app.js

定时任务,每天23点59分定时清理临时文件

// 每天23点59分删除临时文件
cron.schedule("59 23 * * *", function() {console.log("---------------------");console.log("Cron Job Start");const files = fs.readdirSync(path.resolve(__dirname, '../db/__temp__'));if(files.length > 0) {files.forEach(file => fs.unlinkSync(path.resolve(__dirname, `../db/__temp__/${file}`)));}console.log("Cron Job Done");console.log("---------------------");
});

ecosystem.config.js

pm2 相关的一些配置,这里开启了3个实例进行监听

module.exports = {apps: [{name: 'server',script: './server',exec_mode: 'cluster',instances: 3,max_restarts: 4,min_uptime: 5000,max_memory_restart: '1G'}]
}

总结

前端设计走查平台的后端接口部分核心在于图片的比对,而对于图片相似度的比较,通常又会涉及到图像处理相关的内容,这里使用的是 looks-same 这个开源库,其本质利用像素之间的匹配来计算相似度,另外还有利用余弦相似度、哈希算法、直方图、SSIM、互信息等,除了这些传统方法外,还可以使用深度学习的方法来处理,常见的有如特征值提取+特征向量相似度计算的方法等等,这就涉及到了前端智能化的领域,对于这部分感兴趣的同学可以参看一下蚂蚁金服的蒙娜丽莎这个智能化的设计走查平台的实现(ps:更智能的视觉验收提效方案 - 申姜)。在前端智能化领域中,通常应用场景都是与上游设计部分的落地中,比如D2C和C2D领域,对于前端智能化方向感兴趣的同学,可以着重在这个角度多多研讨,共勉!!!

参考

  • 你知道pm2是怎么工作的吗
  • Node的Cluster模块和PM2 的原理介绍
  • Linux 惊群效应之 Nginx 解决方案
  • 设计小姐姐都说好的视觉还原对比利器
  • 重点:机器学习总结之各算法常用包和函数
  • 图片相似度计算方法总结

前端设计走查平台实践(后端篇)相关推荐

  1. 前端设计走查平台实践(前端篇)

    项目背景 随着前端业务的不断发展,前端对设计稿的还原程度也成为了影响用户对产品体验的一个关键指标,作为最靠近用户侧的研发,前端工程师通常需要和设计师同学通力配合来提升用户体验.其中,设计走查是设计同学 ...

  2. vivo浏览器的快速开发平台实践-总览篇

    一.什么是快速开发平台 快速开发平台,顾名思义就是可以使得开发更为快速的开发平台,是提高团队开发效率的生产力工具.近一两年,国内很多公司越来越注重研发效能的度量和提升,基于软件开发的特点,覆盖管理和优 ...

  3. 微前端在平台级管理系统中的最佳实践

    微前端在平台级管理系统中的最佳实践 一.什么是微前端 二.什么是通用管理端工程 三.当管理端工程遇上微前端 四.未来展望 作者:杨朋飞 一.什么是微前端 近十年来,前端技术有了长足发展,各种概念与框架 ...

  4. 前端和后端开发人员比例_前端开发人员vs后端开发人员–实践中的定义和含义

    前端和后端开发人员比例 Websites and applications are complex! Buttons and images are just the tip of the iceber ...

  5. 平台篇-58 HBase 平台实践和应用

    HBase 是一个基于 Hadoop 的分布式.面向列的 Key-Value 存储系统,可以对需 要实时读写.随机访问大规模数据集的场景提供高可靠.高性能的服务,在大数 据相关领域应用广泛.HBase ...

  6. 蒲公英平台用法的最佳实践(Android篇)

    蒲公英平台(http://www.pgyer.com)用法的最佳实践(Android篇) 蒲公英主要提供了app托管分发和SDK功能(异常上报,摇一摇反馈,新版本检测),当然还有各种各样的客户端(ma ...

  7. 监控平台前端SDK开发实践

    监控是提高故障处理能力和保障服务质量必需的一环,它需要负责的内容包括:及时上报错误.收集有效信息.提供故障排查依据. 及时上报错误:发生线上问题后,经由运营或者产品反馈到开发人员,其中流转过程可能是几 ...

  8. 埋点用例管理_API管理平台之系统设计篇

    导语 根据接口开发过程中的一系列痛点,提供接口文档管理,接口Mock,接口调试.测试等有效解决方案,使前后端接口相关工作更加高效. 背景 互联网的高速发展,API变得至关重要.随着API数量持续增多, ...

  9. 高性能平台设计——美团旅行结算平台实践

    点击蓝字订阅,不错过下一篇好文章 本文根据第23期美团点评技术沙龙演讲内容整理而成. 酒旅有很多条业务线,例如酒店.门票.火车票等等,每种业务都有结算诉求,而结算处于整个交易的最后一环不可缺少,因此我 ...

最新文章

  1. Linux环境编程--编辑器基本操作
  2. C/S与B/S架构的区别和优缺点
  3. 安卓能不能安装jar_Sentaurus TCAD安装之jedit+TCAD宏包
  4. Android之BroadcastReceiver 监听系统广播
  5. 鸿蒙os开发小程序,9岁小学生展示鸿蒙OS开发:这操作太秀了
  6. sqlserver两个日期之间的年数_SQL语句计算两个日期之间有多少个工作日的方法
  7. C罗捧得史上首个区块链得分王奖杯 1600名支付宝用户获“同款”
  8. 同一个ip服务器comcat下部署第二个项目怎么设置,用nginx在同一服务器端口下部署多个项目,第二个项目打开后页面空白?...
  9. [转用 jsfl 扩展你的 flash
  10. oracle忘记sys密码处理
  11. apache设置域名绑定 以及绑定不起作用的排查.
  12. Unity 手动下载汉化包并安装
  13. 大学生网课查题公众号搭建使用
  14. 模拟、数字基带/频带通信系统:编码、信源/信道编码、调制、码间串扰
  15. Presto Facebook 开源的大数据查询引擎
  16. Swiper的安装及使用
  17. 暴走欧洲之文明的迭代
  18. 按阶段划分软件测试:单元测试 , 集成测试 , 系统测试 , 验收测试
  19. python改变列的数据类型_更改Pandas中列的数据类型
  20. 8.内核级线程(核心级线程)

热门文章

  1. [龙讯6号]龙芯2E首次公布设计细节
  2. 关于SQLite数据库 字段 DateTime 类型
  3. android使用NFC的读模式
  4. 【设计模式】2、创建型模式
  5. Hype Cycle (技术成熟度曲线)
  6. Java+SSM求职招聘系统兼职应聘(含源码+论文+答辩PPT等)
  7. LINQ读书笔记(一)
  8. 转:背诵新概念,事半功倍
  9. kodi android 卡顿,给Kodi设置缓存来解决播放大文件卡顿
  10. C++【坑人神器:绝地求生小游戏5.0】全新时代!