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

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

后端 | 前端设计走查平台实践(后端篇).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领域,对于前端智能化方向感兴趣的同学,可以着重在这个角度多多研讨,共勉!!!

参考

相关文章
|
6天前
|
API 持续交付 开发者
后端开发中的微服务架构实践与挑战
在数字化时代,后端服务的构建和管理变得日益复杂。本文将深入探讨微服务架构在后端开发中的应用,分析其在提高系统可扩展性、灵活性和可维护性方面的优势,同时讨论实施微服务时面临的挑战,如服务拆分、数据一致性和部署复杂性等。通过实际案例分析,本文旨在为开发者提供微服务架构的实用见解和解决策略。
|
19天前
|
Java 持续交付 微服务
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在现代后端开发中的应用,通过具体案例分析,揭示了其如何助力企业应对业务复杂性、提升系统可维护性和可扩展性。文章首先概述了微服务的核心概念及其优势,随后详细阐述了实施微服务过程中的关键技术选型、服务拆分策略、容错机制以及持续集成/持续部署(CI/CD)的最佳实践。最后,通过一个真实世界的应用实例,展示了微服务架构在实际项目中的成功应用及其带来的显著成效。 ####
|
14天前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
2天前
|
消息中间件 设计模式 运维
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在现代后端开发中的应用,通过实际案例分析,揭示了其在提升系统灵活性、可扩展性及促进技术创新方面的显著优势。同时,文章也未回避微服务实施过程中面临的挑战,如服务间通信复杂性、数据一致性保障及部署运维难度增加等问题,并基于实践经验提出了一系列应对策略,为开发者在构建高效、稳定的微服务平台时提供有价值的参考。 ####
|
2天前
|
编解码 前端开发 UED
探索无界:前端开发中的响应式设计深度解析与实践####
【10月更文挑战第29天】 本文深入探讨了响应式设计的核心理念,即通过灵活的布局、媒体查询及弹性图片等技术手段,使网站能够在不同设备上提供一致且优质的用户体验。不同于传统摘要概述,本文将以一次具体项目实践为引,逐步剖析响应式设计的关键技术点,分享实战经验与避坑指南,旨在为前端开发者提供一套实用的响应式设计方法论。 ####
21 4
|
2天前
|
消息中间件 监控 数据管理
后端开发中的微服务架构实践与挑战####
【10月更文挑战第29天】 在当今快速发展的软件开发领域,微服务架构已成为构建高效、可扩展和易于维护应用程序的首选方案。本文探讨了微服务架构的核心概念、实施策略以及面临的主要挑战,旨在为开发者提供一份实用的指南,帮助他们在项目中成功应用微服务架构。通过具体案例分析,我们将深入了解如何克服服务划分、数据管理、通信机制等关键问题,以实现系统的高可用性和高性能。 --- ###
21 2
|
5天前
|
存储 监控 NoSQL
构建高效后端服务:从理论到实践
【10月更文挑战第30天】在数字化时代,后端服务是支撑起整个互联网的基石。一个高效、稳定且可扩展的后端系统对于任何在线业务都是至关重要的。本文将带你了解如何从零开始构建一个高效的后端服务,涵盖了设计思路、关键技术选型、开发流程以及性能优化等方面。我们将通过实际的代码示例和案例分析,深入探讨如何实现一个既快速又可靠的后端系统。无论你是后端开发的新手还是有经验的开发者,这篇文章都将为你提供宝贵的参考和启示。
20 3
|
11天前
|
存储 SQL 缓存
构建高效后端服务:从理论到实践
在当今的软件开发领域,后端服务扮演着至关重要的角色。它不仅支撑着应用程序的核心功能,还影响着系统的性能、可扩展性和用户体验。本文将深入探讨如何构建一个高效的后端服务,涵盖从需求分析到架构设计,再到技术选型和性能优化的全过程。我们将通过实际案例,展示如何在保证数据一致性和安全性的前提下,实现高并发处理和快速响应。无论你是后端开发的新手还是有经验的工程师,这篇文章都将为你提供宝贵的见解和实用的建议。
|
12天前
|
缓存 运维 监控
后端开发中的微服务架构实践与挑战#### 一、
【10月更文挑战第22天】 本文探讨了微服务架构在后端开发中的应用实践,深入剖析了其核心优势、常见挑战及应对策略。传统后端架构难以满足快速迭代与高可用性需求,而微服务通过服务拆分与独立部署,显著提升了系统的灵活性和可维护性。文章指出,实施微服务需关注服务划分的合理性、通信机制的选择及数据一致性等问题。以电商系统为例,详细阐述了微服务改造过程,包括用户、订单、商品等服务的拆分与交互。最终强调,微服务虽优势明显,但落地需谨慎规划,持续优化。 #### 二、
|
18天前
|
监控 API 开发者
后端开发中的微服务架构实践与优化
【10月更文挑战第17天】 本文深入探讨了微服务架构在后端开发中的应用及其优化策略。通过分析微服务的核心理念、设计原则及实际案例,揭示了如何构建高效、可扩展的微服务系统。文章强调了微服务架构对于提升系统灵活性、降低耦合度的重要性,并提供了实用的优化建议,帮助开发者更好地应对复杂业务场景下的挑战。
17 7