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

简介: 设计师在进行走查的过程中,肉眼的比对偶尔会忽略一些细微部分,同时也会耗费设计师大量的精力,为了辅助设计同学能够更高效的进行设计走查,本文旨在通过设计走查平台在后端侧的实践总结下对于视觉稿还原程度比对的一些思路。

后端 | 前端设计走查平台实践(后端篇).png

项目背景

随着业务的不断发展,研发链路的效能提升也是一个至关重要的指标,其中对前端工程基建而言,其上游部分主要是和设计师同学打交道,而在整个研发链路中,通常会有设计走查的流程来让设计师同学辅助测试同学完成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: 
        - putImage
      requestBody:
        required: true
        content: 
          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: 
        - compareImage
      requestBody:
        required: true
        content: 
          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 differences
        strict: false, // strict comparsion
        tolerance: 2.5,
        antialiasingTolerance: 0,
        ignoreAntialiasing: true, // ignore antialising by default
        ignoreCaret: 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: 
        - downloadImage
      requestBody:
        required: true
        content: 
          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领域,对于前端智能化方向感兴趣的同学,可以着重在这个角度多多研讨,共勉!!!

参考

相关文章
|
11月前
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
1014 1
|
7月前
|
存储 前端开发 安全
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
实现“永久登录”:针对蜻蜓Q系统的用户体验优化方案(前端uni-app+后端Laravel详解)-优雅草卓伊凡
306 5
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
674 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
12月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
647 70
|
11月前
|
Java 关系型数据库 MySQL
在Linux平台上进行JDK、Tomcat、MySQL的安装并部署后端项目
现在,你可以通过访问http://Your_IP:Tomcat_Port/Your_Project访问你的项目了。如果一切顺利,你将看到那绚烂的胜利之光照耀在你的项目之上!
528 41
|
11月前
|
存储 消息中间件 前端开发
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践
校园圈子系统校园论坛小程序采用uni-app前端框架,支持多端运行,结合PHP后端(如ThinkPHP/Laravel),实现用户认证、社交关系管理、动态发布与实时聊天功能。前端通过组件化开发和uni.request与后端交互,后端提供RESTful API处理业务逻辑并存储数据于MySQL。同时引入Redis缓存热点数据,RabbitMQ处理异步任务,优化系统性能。核心功能包括JWT身份验证、好友系统、WebSocket实时聊天及活动管理,确保高效稳定的用户体验。
574 4
PHP后端与uni-app前端协同的校园圈子系统:校园社交场景的跨端开发实践
|
前端开发 算法 NoSQL
前端uin后端php社交软件源码,快速构建属于你的交友平台
这是一款功能全面的社交软件解决方案,覆盖多种场景需求。支持即时通讯(一对一聊天、群聊、文件传输、语音/视频通话)、内容动态(发布、点赞、评论)以及红包模块(接入支付宝、微信等第三方支付)。系统采用前后端分离架构,前端基于 UniApp,后端使用 PHP 框架(如 Laravel/Symfony),配合 MySQL/Redis 和自建 Socket 服务实现高效实时通信。提供用户认证(JWT 集成)、智能匹配算法等功能,助力快速上线,显著节约开发成本。
452 2
前端uin后端php社交软件源码,快速构建属于你的交友平台
|
10月前
|
人工智能 监控 前端开发
AI工具:前端与后端的终极对决?谁将成为新时代的宠儿?
深入探讨AI工具对前端和后端开发的具体影响、各自的机遇与挑战,并分析未来开发者如何驾驭AI,实现能力跃迁。
606 0
|
12月前
|
人工智能 缓存 Java
用 AI 搭建秒杀平台后端,一周搞定所有功能(附超详细踩坑记录)
本文分享如何借助AI技术快速搭建电商秒杀平台后端。通过飞算JavaAI,从需求分析到代码生成全流程智能化,大幅提高开发效率。文章详细记录了技术栈选择(Java、Spring Boot、MySQL、Redis)、系统架构设计、缓存机制优化、数据一致性保障及测试调优等环节,解决高并发难题,助开发者高效完成秒杀平台构建并规避常见坑点。
|
监控 前端开发 Java
构建高效Java后端与前端交互的定时任务调度系统
通过以上步骤,我们构建了一个高效的Java后端与前端交互的定时任务调度系统。该系统使用Spring Boot作为后端框架,Quartz作为任务调度器,并通过前端界面实现用户交互。此系统可以应用于各种需要定时任务调度的业务场景,如数据同步、报告生成和系统监控等。
681 9

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
    984
  • 2
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
    420
  • 3
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
    330
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(七):学习ransform属性;本文学习 rotate旋转、scale缩放、skew扭曲、tanslate移动、matrix矩阵 多个参数
    303
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
    421
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
    608
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
    773
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(三):元素继承关系、层叠样式规则、字体属性、文本属性;针对字体和文本作样式修改
    214
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
    621
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
    382