【Modelground】个人AI产品MVP迭代平台(3)——工程化架构设计

文章目录

  • 背景
  • monorepo
  • 多项目调试/打包
  • 公共静态资源服务
  • 公共模型拷贝入项目的public文件夹
  • 总结

背景

Modelground中的项目,基本都依赖Mediapipe模型,因此,有很强的需要对Mediapipe进行封装,其余项目都调用这个封装库。从架构上,这种结构的项目很容易联想到Monorepo,即多项目管理。现代包管理器对monorepo形式的仓库已有较好的支持,例如yarn、lerna等。Modelground采用的是其中的一种:pnpm

架构示意图如下:

架构示意图

monorepo

首先全局安装pnpm

npm i pnpm -g

项目初始化

pnpm init

创建pnpm-workspace.yaml,定义包目录

packages:
  - 'packages/**'

创建packages文件夹,添加第项目A和项目B

mkdir packages
cd packages
pnpm create vite A
pnpm create vite B

此时,packages中会出现名称为A和B的两个项目文件夹。

如果项目B要依赖A:

pnpm add A --filter B

此时,B项目的packages.json如下:

{
	"dependencies": {
    	"A": "workspace:^",
  },
}

这样,B打包时,A包不需要发布成npm包,B就可以将A一同打包进dist。
同理,需要给B项目添加某个依赖包C,也是如下代码:

pnpm add C --filter B

最后,统一安装整个项目的包

pnpm i

多项目调试/打包

Modelground中的项目有依赖关系,例如B依赖A,有时候我们会同时修改A和B项目的代码,如果每次都要手动启动两个项目,步骤过于繁琐,因此强需求一套自动化代码去调试某个项目前,自动开启其依赖项目。

我们首先需要一个终端的命令行选项,根目录安装inquirer

pnpm add inquirer --D -i

其次,在js文件中更方便地执行shell,需要安装execa

pnpm add execa --D -i

命令行写法:

inquirer
      .prompt([
        {
          type: "list",
          message: `选择要启动的项目:`,
          name: "mono",    // 存储答案的字段
          default: 'home',   // 默认启动项
          choices: ['home', 'fitness-count', 'ml-video', 'shooter-game', 'generate-ai'], // 想启动的项目名列表
        }
      ]).then({mono: prd} => {
      	  console.log(prd)
		  // 选择启动的项目名,例如"home"
	  })

在这里插入图片描述
启动项目的代码:

const projectServer = execa('pnpm', ['--F', prd, 'run', 'dev'], { stdio: 'pipe' }); // 等价 $pnpm --F prd run dev
projectServer.stdout.on('data', (data) => { console.log(data) }); // 监听运行输出
projectServer.stderr.on('data', (data) => { console.error(data) }); // 监听报错输出

如何在项目A启动完成后,再启动B?
一种解法是在A项目启动后,监听stdout中的输出信息,如果出现"built in",就启动B,代码如下:

let hasRun = false;
const A = execa('pnpm', ['--F', 'A', 'run', 'dev'], { stdio: 'pipe' });
A.stdout.on('data', (data) => {
        console.log(data)
        // A运行起来后,再运行当前启动项目,仅运行一次
        if (data.includes('built in') && !hasRun) {
          hasRun = true;
          const B = execa('pnpm', ['--F', 'B', 'run', 'dev'], { stdio: 'pipe' });
          B.stdout.on('data', (data) => { console.log(data) });
          B.stderr.on('data', (data) => { console.error(data) });
        }
      });
modelServer.stderr.on('data', (data) => { console.error(data) });

如果想区分不同项目的输出信息,可以安装一个chalk,可以用调整输出文字的颜色:

// 当前项目用绿色加粗
function projectTitle() {
  return chalk.green.bold('当前项目服务:');
}

// 公共静态文件用黄色加粗
function publicTitle() {
  return chalk.yellow.bold('公共模型服务:');
}

// 模型依赖用蓝色加粗
function modelTitle() {
  return chalk.blue.bold('mediapipe模型服务:');
}

// stdOut用白色
function stdOut(data) {
  return chalk.white(data);
}

// stdErr用红色
function stdErr(data) {
  return chalk.red(data);
}

// 改造上述代码
A.stdout.on('data', (data) => { console.log(projectTitle(), stdOut(data)) });
A.stderr.on('data', (data) => { console.log(projectTitle(), stdErr(data)) });

效果图:
在这里插入图片描述
同理,也可以实现多项目打包代码同步远程仓库,这里就不赘述。

公共静态资源服务

Mediapipe模型所需的预训练模型体积相对较大,由于内部采用fetch方法去请求预训练模型,因此没法放在公共依赖包中,只能放在项目的public下。但是如果每个项目都去存放一些模型,往往有重复问题,因此强需求一个公共静态资源服务,将所有的预训练模型提取到一个公共目录下。

在packages下创建一个public-assets文件夹,将公共模型都放入该文件夹下。
在这里插入图片描述
创建一个server.js,写一个简单的node文件服务。

// 引入http模块
const http = require('http');
// 引入fs模块
const fs = require('fs');
// 引入path模块
const path = require('path');

// 创建HTTP服务器
const server = http.createServer((req, res) => {
  // 构建请求的文件路径
  const filePath = path.join(__dirname, '/', req.url === '/' ? 'index.html' : req.url);
  // 检查文件是否存在
  fs.exists(filePath, (exist) => {
    if (!exist) {
      // 如果文件不存在,返回404
      res.writeHead(404, { 'Content-Type': 'text/html' });
      res.end('404 Not Found');
      return;
    }

    // 读取文件内容
    fs.readFile(filePath, (err, content) => {
      if (err) {
        res.writeHead(500, { 'Content-Type': 'text/html' });
        res.end('500 Internal Server Error');
      } else {
        // 设置响应头
        const extname = path.extname(filePath);
        let contentType = 'text/plain';
        if (extname === '.task') {
          contentType = 'application/octet-stream'
        } else if (extname === '.wasm') {
          contentType = 'application/wasm';
        } else if (extname === '.tflite') {
          contentType = 'application/octet-stream';
        }
        res.writeHead(200, {
        	'Content-Type': contentType,
        	"access-control-allow-origin": "*", // 解决不同端口跨域问题
        });
        res.end(content, 'utf-8');
      }
    });
  });
});

// 设置监听端口
const port = 5180;
server.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/`);
});

通过 node server.js 就可以开启该文件服务。

我们在每个项目中新建环境变量文件.env.development

VITE_MODEL_PATH=http://localhost:5180/

在实际请求模型时,通过vite的环境变量就可以取到该变量import.meta.env.VITE_MODEL_PATH

同理,在正式环境时,模型的请求地址就变成了项目的路由,如果是根路由,设定.env.production

VITE_MODEL_PATH=/

这样,不同环境下就能正常获取模型文件。

公共模型拷贝入项目的public文件夹

简单说,就是利用shell的cp命令,拷贝文件,直接上build代码:

import inquirer from "inquirer";
import { execaCommand } from "execa";

// 各项目所需的模型文件
const copyConfig = {
  'shooter-game': {
    'packages/public-assets/wasm/vision_wasm_internal.js': 'packages/shooter-game/dist/wasm',
    'packages/public-assets/wasm/vision_wasm_internal.wasm': 'packages/shooter-game/dist/wasm',
    'packages/public-assets/models/ObjectDetection/rim_ball_model_v1.tflite': 'packages/shooter-game/dist/models/ObjectDetection'
  },
  'ml-video': {
    "packages/public-assets/models/ObjectDetection/rim_ball_model_v1.tflite": 'packages/ml-video/dist/models/ObjectDetection',
    "packages/public-assets/models/ObjectDetection/efficientdet_lite0.tflite": 'packages/ml-video/dist/models/ObjectDetection',
    "packages/public-assets/models/ObjectDetection/efficientdet_lite2.tflite": 'packages/ml-video/dist/models/ObjectDetection',
    "packages/public-assets/models/PoseLandMarker/pose_landmarker_full.task": 'packages/ml-video/dist/models/PoseLandMarker',
    "packages/public-assets/models/PoseLandMarker/pose_landmarker_lite.task": 'packages/ml-video/dist/models/PoseLandMarker',
    "packages/public-assets/models/HandLandMarker/hand_landmarker.task": 'packages/ml-video/dist/models/HandLandMarker',
    "packages/public-assets/models/FaceLandMarker/face_landmarker.task": 'packages/ml-video/dist/models/FaceLandMarker',
    "packages/public-assets/wasm/vision_wasm_internal.js": 'packages/ml-video/dist/wasm',
    "packages/public-assets/wasm/vision_wasm_internal.wasm": 'packages/ml-video/dist/wasm'
  }
}

async function run() {
  try {
    const { mono: prd } = await inquirer
      .prompt([
        {
          type: "list",
          message: `选择要构建的项目:`,
          name: "mono",    // 存储答案的字段
          default: 'home',   // 默认启动项
          choices: ['home', 'fitness-count', 'mediapipe-model-core', 'ml-video', 'shooter-game', 'generate-ai'],
        }
      ]);
    // 先打包,有了dist文件夹再拷贝文件
    let result = await execaCommand(`pnpm --filter ${prd} run build`, { stdio: "inherit" });
    
    const copy = copyConfig[prd];
    if (!copy) return;
    
    const pa = Object.entries(copy);
    for (let i = 0; i < pa.length; i++) {
      const from = pa[i][0]; // 待拷贝的文件
      const to = pa[i][1]; // 目标文件夹
      await execaCommand(`mkdir -p ${to}`); // 如果没有该文件夹,先新建
      await execaCommand(`cp ${from} ${to}`); // 执行拷贝
    }
  } catch (err) {
    console.error(err);
  }
}

run();

总结

这套架构是我在开发Modelground过程中,逐渐摸索出来的比较成熟的架构。很多坑都是过程中发现并解决,并不是一开始就能考虑到的。

总结而言,依赖monorepo多项目管理模式,实现项目依赖,并行开发。通过流水线模式,简化项目启动流程。通过公共模型服务,减少冗余静态文件复制动作,在打包时统一拷贝。

以上,就是Modelground的工程化架构设计内容,极大减少了本人开发耗时,可以将精力集中在构思创意上。

欢迎访问Modelground体验已有模型https://tryiscool.space

在这里插入图片描述
如果本文对你有帮助,希望能得到你的三连+订阅Modelground专栏,鼓励我持续产出,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/683481.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IIS漏洞

IIS7.5解析漏洞 安装IIS7.5 安装完成之后直接访问浏览器&#xff1a; 安装phpstudy for IIS 安装这个的目的是方便&#xff0c;不用自己去配置 解压开傻瓜式安装即可。然后查看探针&#xff1a; 漏洞原理 IIS7/7.5在Fast-CGI运行模式下,在一个文件路径(/shell.jpg)后面加上/…

linux线程的同步与互斥

前面我们讲了线程的概念以及如何创建与控制线程&#xff0c;接下来我们来对线程的细节与线程之间的问题进行一些讲解&#xff1b; 1.线程的互斥 互斥就是相互排斥&#xff0c;我们可以理解为对立竞争不相容&#xff1b;线程的互斥则是线程之间在对于临界资源竞争时相互排斥的…

openh264 编码命令行工具源码分析

openh264 OpenH264 是由 Cisco 公司发布的一个开源的 H.264 编码和解码器。它提供了命令行工具&#xff0c;可以用于对视频进行编码和解码操作。 使用说明 openh264 编码命令行工具可以使用命令行或 config 配置进行编码操作。编译和使用方法具体可以参考 Windows11编译open…

12_JavaWebAjax

文章目录 Ajax1. 同步请求异步请求2. Ajax实现方式3. 日程管理第四期4. 响应JSON串4.1 响应JSON串格式的一般格式 Appendix Ajax 发送请求的一些方式 1.输入浏览器回车 2.html>head>script/link ​ img标签 3.a标签form表单标签等 用户手动控制提交产生&#xff1b…

实验七、创建小型实验拓扑《计算机网络》

早检到底是谁发明出来的。 一、实验目的 完成本实验后&#xff0c;您将能够&#xff1a; • 设计逻辑网络。 • 配置物理实验拓扑。 • 配置 LAN 逻辑拓扑。 • 验证 LAN 连通性。 二、实验任务 在本实验中&#xff0c;将要求您连接网络设备并配置主机实现基本的网络…

R语言探索与分析20-北京市气温预测分析

一、序言 近年来&#xff0c;人类大量燃烧煤炭、天然气等含碳燃料导致温室气 体过度排放&#xff0c;大量温室气体强烈吸收地面辐射中的红外线&#xff0c;造 成温室效应不断累积&#xff0c;使得地球温度上升&#xff0c;造成全球气候变暖。气象温度的预测一直以来都是天气预…

计算机毕业设计 | 基于node(Koa)+vue 高校宿舍管理系统 宿舍可视化(附源码)

1&#xff0c;绪论 1.1 项目背景 随着科技的发展&#xff0c;智能化管理越来越重要。大学生在宿舍的时间超过了1/3&#xff0c;因此良好的宿舍管理对学生的生活和学习极为关键。学生宿舍管理系统能够合理安排新生分配宿舍&#xff0c;不浪费公共资源&#xff0c;减轻学校管理…

微信扫普通二维码后通过小程序观看的实现

为了方便小程序开发者更便捷地推广小程序&#xff0c;兼容线下已有的二维码&#xff0c;微信公众平台开放扫描普通链接二维码跳转小程序能力。 功能介绍 普通链接二维码&#xff0c;是指开发者使用工具对网页链接进行编码后生成的二维码。 线下商户可不需更换线下二维码&…

【面试干货】SQL中count(*)、count(1)和count(column)的区别与用法

【面试干货】SQL中count&#xff08;*&#xff09;、count&#xff08;1&#xff09;和count&#xff08;column&#xff09;的区别与用法 1、count(*)2、count(1)3、count(column) &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在SQL中&a…

基于GTX 8B10B编码的自定义PHY上板测试(高速收发器十四)

前文整理了GTX IP&#xff0c;完成了自定义PHY协议的收发模块设计&#xff0c;本文将通过光纤回环&#xff0c;对这些模块上板测试&#xff0c;首先需要编写一个用于生成测试数据的用户模块。 1、测试数据生成模块 本模块用于生成自定义PHY协议的测试数据&#xff0c;通过axi_…

【微信小程序】页面导航

声明式导航 导航到 tabbar 页 tabBar页面指的是被配置为tabBar的页面。 在使用<navigator>组件跳转到指定的tabBar页面时&#xff0c;需要指定url属性和open-type属性&#xff0c;其中&#xff1a; url 表示要跳转的页面的地址&#xff0c;必须以/开头open-type表示跳…

Python轻量级嵌入式关系数据库之apsw使用详解

概要 在现代应用开发中,数据库是一个非常重要的组成部分。SQLite 是一个轻量级的嵌入式关系数据库管理系统,被广泛应用于各种应用程序中。APSW(Another Python SQLite Wrapper)库是一个专门用于访问 SQLite 数据库的 Python 包,它提供了 SQLite 所有的功能,并且比标准库…

usb设备在主机和VMWare虚拟机中切换连接

操作&#xff1a;点击菜单栏虚拟机(M)>可移动设备>选择自己的usb设备>连接(断开与 主机 的连接)

【会议征稿,ACM出版】2024年图像处理、智能控制与计算机工程国际学术会议(IPICE 2024,7月9-11)

2024年图像处理、智能控制与计算机工程国际学术会议&#xff08;IPICE 2024&#xff09;将于2024年7月9-11日在中国福州举行。本届会议由阳光学院、福建省空间信息感知与智能处理重点实验室、空间数据挖掘与应用福建省高校工程研究中心联合主办。 会议主要围绕图像处理、智能控…

巨详细Linux安装Tomcat教程

巨详细Linux安装Tomcat教程 1、检查是否残留其他版本2、上传安装包至服务器2.1安装包获取2.2创建相关目录 3、安装Tomcat3.1安装3.2启动3.3web页面 4、配置Tomcat4.1把tomcat进程交给systemctl管理4.2设置tomcat开机自启动 1、检查是否残留其他版本 #检查残留数据 rpm -qa|gre…

Eclipse添加C和C++编译成汇编文件的选项

在miscellaneous中添加assemble listing选项就可以生成汇编文件了

C++标准模板(STL)- 迭代器库-迭代器适配器- 逆序遍历的迭代器适配器 (二)

迭代器库-迭代器原语 迭代器库提供了五种迭代器的定义&#xff0c;同时还提供了迭代器特征、适配器及相关的工具函数。 迭代器分类 迭代器共有五 (C17 前)六 (C17 起)种&#xff1a;遗留输入迭代器 (LegacyInputIterator) 、遗留输出迭代器 (LegacyOutputIterator) 、遗留向前…

网站安全小白也能搞定的SSL证书安装免费方法

大家都知道&#xff0c;部署一个网站&#xff0c;除了购买域名&#xff0c;现在基本标配SSL证书。 我们以aliyun为例 大家看到这个&#xff0c;收费的SSL证书几千-几万1年不等。这时候&#xff0c;你就会想有没有免费的可以搞。linux老鸟都知道&#xff0c; Let’s Encrypt 、…

微信小程序多端框架打包后发布到华为市场

app上架华为应用市场 一、android 发布到华为应用市场 1、华为应用市场注册开发者账号 https://developer.huawei.com/consumer/cn/?ha_sourcesem&ha_sourceId89000605 2、进行企业认证 3、app隐私弹窗 miniapp-privacy.json 1、协议弹窗内容&#xff1a; {"tit…