2024年11月

好未来,前身学而思,于 2010 年在美国纽约证券交易所上市。公司积极将大模型研究应用于教学产品中,近期推出了数学领域的千亿级大模型。

在大模型的背景下,存储系统需处理巨量数据和复杂文件操作,要求支持高并发和高吞吐量。此外,还需应对版本管理、模型训练性能优化和多云分发的挑战。

为解决这些问题,团队基于 JuiceFS 开发了一个模型仓库,支持用户训练过程存储 checkpoint,并且控制面支持用户从各个云环境上传并统一管理模型。通过 JuiceFS CSI 组件,好未来将模型仓库挂载到各个集群中,大模型文件挂载配置只需 1-3 分钟,使得 AI 应用弹性变得更加容易。

此外,通过实施权限控制、克隆备份等策略,有效减少了用户误操作的损失并提高了数据安全性。目前好未来在多云多地部署了两套元数据和数据仓库;对象存储的使用规模达 6TB,存储超过 100 个模型。

01 大模型背景下模型仓库的挑战

在以往传统的 DevOps 研发流程中,我们通常以容器镜像为交付物,即由 Docker Builder 构建出一个镜像,然后通过 docker push 命令将其推送至 Docker Hub 或其他镜像仓库中。客户端的数据面则会通过 Docker 的方式,从中心镜像仓库拉取镜像至本地,这一过程中可能会采用一些加速手段以提高效率。

到 AI 场景时,情况就有所不同了。以训练任务为例,它可能会使用 Torch Save、Megatron save_checkpoint或其他方式生成一个序列化的文件,这个文件随后会以 Linux POSIX 方式写入到存储中,这个存储可以是对象存储(如阿里云的 OSS)或文件系统(如 GPFS 或 NFS)。简而言之,训练任务通过写文件系统的方式,将模型写入到远程存储中,从而实现了模型的上传。整个过程中还包含一些评估步骤,但在此我们略去不谈,以精简描述整个流程。与传统 IT 交付中仅仅涉及镜像的推送和拉取不同,AI 场景需要处理更大规模的数据和更复杂的文件操作,对存储系统的要求更为苛刻,常常需要高并发和高吞吐量的支持。

到了推理阶段,在容器的场景下,需要通过 CSI 方式去挂载 NFS 或者 GPFS 系统或对象存储系统来达到将远程的模型拉取到容器场景中。从表面上看,这个流程似乎并无问题,也能够正常工作。然而,在实际运行过程中,我们还是发现了不少明显的问题。

  • 首要问题是缺乏版本管理
    。对于传统的容器镜像,我们有明确的交付物和版本信息,包括上传者、大小等详细信息。然而,在模型仓库中,由于模型通常是以 Linux 文件系统的方式存储的(文件或文件夹形式),因此缺乏版本管理和元数据信息,如上传者、修改者等。

  • 其次,模型仓库无法实现加速和多云分发
    。在 Docker 场景中,我们可以使用如 Dragonfly 等工具进行加速。但是,在使用 NFS、GFS 或 OSS 等存储系统时,却缺乏一个有效的加速方案。同时,也无法实现多云分发。以 NFS 为例,它基本上是闭环于一个 IDC 内部的,无法实现跨 IDC 甚至跨云的挂载。即使能够实现挂载,其速度也会非常慢,因此我们可以认为它无法实现多云分发。

  • 最后,安全性差
    。在推理场景下,整个模型仓库需要挂载到容器中。如果客户端的挂载权限过大(例如挂载了一个包含大量模型的目录),就可能导致模型泄露或误删除的问题。特别是当挂载方式为可读可写时,客户端甚至有可能删除模型文件。

由此衍生出不同场景对模型仓库的存储需求。

训练场景的存储需求:

  • 模型下载与处理
    :在算法建模阶段,需要下载并可能转换及切分各种模型,这包括开源模型、参考模型或自设计的网络结构。例如,进行模型的 TP(Tensor Parallelism)和 PP(Pipeline Parallelism)切分。
  • 高性能读写
    :训练阶段要求存储系统具备极高的读写吞吐能力,以便存储大规模的 checkpoint 文件。这些文件可能非常庞大,如单个 checkpoint 文件大小接近 1TB。

推理场景的存储需求:

  • 模型版本管理与服务化
    :当模型更新至新版本时,需要进行版本发布和审批流程。此外,在模型服务化过程中,可能需要频繁地扩展或收缩使用的 GPU 资源,这通常在夜间进行资源释放,在白天进行资源扩展。
  • 高读吞吐性能
    :由于白天需频繁拉取模型副本以应对资源扩展,存储系统需支持高效的读操作,确保快速响应模型拉取需求。

此外,从管理者角度存在以下需求:

  • 团队级模型管理
    :模型仓库应支持按团队进行模型的隔离管理,确保不同团队之间模型的隐私和独立性。
  • 详尽的版本控制
    :存储系统应能清晰记录模型的迭代时间、版本用途等元信息,支持模型的上传、下载、审计和分享功能。

02 存储选型:如何在成本、性能、稳定性之间取舍?

核心考量点

首先,要降低对云厂商的依赖
,确保技术方案在自建 IDC 以及多个云厂商之间保持一致性和统一性;

其次,成本也是一个重要的考量因素
。虽然资金充足可以支持更好的解决方案,但成本效益分析同样重要。我们需要综合考虑各种存储方案的成本,包括本地磁盘、GPFS 以及对象存储(如 OSS)等;

第三,性能是关键因素
。根据之前的背景介绍,模型仓库的读写性能都有较高要求。因此,我们需要在单个 IDC 或单个云上实现模型读写流量的闭环,以确保高性能;

最后,稳定性也是不可忽视的因素
。我们不会为了支撑模型仓库而引入过高的运维复杂度。因此,对组件的繁杂程度和稳定性都有很高的要求。

主要技术选型对比

Fluid + Alluxio + OSS
:该方案在前几年已经相对成熟并受到广泛关注。它融合了云原生的编排能力和对象存储的加速能力,实现了多云技术上的统一。无论是在阿里云、腾讯云还是自建的 IDC 中,都能采用这一方案。此外,该方案在社区的应用也相当广泛。

然而,它也存在一些不足。例如,它无法与 Ceph Rados 进行结合,这在我们集团内部是一个已有的技术栈。同时,该方案的运维复杂度较高,组件较多且资源消耗较大。对于大文件的读取速度,该方案的表现也并不理想。此外,客户端的稳定性也有待进一步验证。

GPFS
:它是一个商业并行文件系统,读写性能强劲,且我们集团已经购买了这一产品。此外,GPFS 在处理海量小文件方面也具有显著优势。然而,它的劣势同样明显。首先,它无法实现多云同步,这意味着我们在 IDC 购买的 GPU 无法在其他云上再购买一套 GPFS,成本高昂。其次,GPFS 的容量价格也非常昂贵,是 OSS 的好几倍。

CephFS
:我们集团内对该技术有一定的技术沉淀和优势。然而,它同样无法实现多云同步,运营成本较高。

JuiceFS
:它的优势在于支持多云同步,运维简单,组件较少且观测性较好。成本方面,它基本上只有对象存储的费用,除了元数据管理外,没有其他额外成本。此外,JuiceFS 既可以在云上搭配对象存储使用,也可以在 IDC 搭配 Ceph Rados 使用。综合考虑以上因素,我们选择了 JuiceFS 作为支持大模型模型仓库的底层存储系统。

03 好未来模型仓库实践方案

训练场景中模型仓库读写设计: 单云训练

首先,聚焦于训练场景中的模型仓库读写设置
。我们采用单云训练策略,即在单一云平台上进行模型训练,而非跨多云进行,这主要考虑到实际操作中的可行性和效率。

针对训练场景下的读写需求,我们制定了以下方案:我们将大量 GPU 机器上冗余的 NVMe 磁盘组成一个 ceph 集群,使用 JuiceFS 对接 Rodos, 从而实现 ceph 集群的读写操作。 在模型训练过程中,模型会以 JuiceFS CSI 方式挂载一个盘。当需要执行 checkpoint 存储或加载时,将直接对 Rodos 进行读写操作。
经实测,在大模型训练过程中,checkpoint 写入速度可达到 15GB/s

在元数据管理方面,我们选择了 Redis。相较于 OceanBase 或 TiKV 等复杂元数据管理引擎,我们选择 redis 主要出于以下考虑:
我们仅将其用于大文件的存储,而每个文件的大小可能达到数 GB。因此,我们判断其数据量相对较小,无需采用复杂的元数据管理引擎,以减轻运维负担

推理场景中模型仓库读写设计: 多云推理

与训练场景不同,推理资源通常分布在多云平台上,如阿里云、腾讯云等。在这些云平台上,我们不会购买大量的 NVMe 机器,因为云上本身具备对象存储能力。因此,我们采用了 JuiceFS 的经典模式,即 JuiceFS 加上 Redis,与云厂商的对象存储组成一个集群。在推理过程中,模型文件以只读方式挂载,以避免程序对其进行修改。此外,我们还设计了一个面向多云环境的间歇性同步方案,以确保模型能够同步到所有云的 JuiceFS 上。

在面对某些挑战时,当需要在白天大规模扩容推理服务时,以扩容 HPA(Horizontal Pod Autoscaler)为例,这种定点扩容会导致大量推理服务同时启动,并需要迅速读取大量的模型文件。这种情况下,如果没有本地缓存的支持,带宽消耗将极为巨大。

为了应对这一挑战,我们采用了 “warm up” 策略。即在定时扩容之前,通过预热的方式将即将被读取的模型文件预先加载到缓存中。这样做可以显著提升扩容的弹性速度,确保推理服务能够迅速启动并投入运行。

管理端:模型仓库上传与下载的设计

管理端主要聚焦于上传和下载功能。我们自主开发了一个客户端,该客户端支持通过 S3 协议上传模型文件。S3 网关会接收并转化这些请求,然后与元数据系统进行交互,如 Redis 等。

在我们的应用场景中,还有一项重要的设计是对相同文件进行去重处理。我们采用了类似于 Docker 镜像仓库的设计思路,即为每个文件计算 MD5 值。如果两个文件的 MD5 值相同,则不会进行重复上传。这一设计不仅节省了存储空间,还提高了上传效率。

此外,在更新模型时,我们还会保留一些快照。使用 JuiceFS 快照功能复制文件时,它并不会在 OSS 中新增文件,而只是在元数据中进行记录。这种方式使得我们在进行模型更新和快照保留时更加便捷高效。

04 未来展望

按需同步多云的模型仓库
:我们目前的做法是采取定期批量同步的方式,将 JuiceFS 在某个云上的集群数据进行同步。然而,这种做法相对简单粗暴,可能无法满足未来对于数据同步的更高需求。因此,我们计划进行改进,实现一个标记型的同步系统。该系统将能够识别需要同步的区域,并在收到模型上传事件后,自动将这些数据同步到多云平台上。此外,我们还将引入一些 warm up 策略,以优化数据同步的过程,提高同步效率和准确性。

扩展单机 cache 和分布式 cache
:我们目前单机使用了3T MVME的 cache 方式,在短期内来看,这种方式的容量还是相对充足的。然而,从长远来看,为了满足更大的数据存储和访问需求,我们将基于一致性哈希的原理,在客户端自主研发一个分布式 cache 组件。这个组件将能够更大程度地提升开启的容量和命中率,从而满足未来对于数据存储和访问的更高要求。

希望这篇内容能够对你有一些帮助,如果有其他疑问欢迎加入
JuiceFS 社区
与大家共同交流。

本篇参考:
https://help.salesforce.com/s/articleView?id=sf.data_sandbox_selective_access.htm&type=5

背景:
最近同事刷新sandbox发现点击create不生效,并且无任何提示(后续可能优化)。

习惯了直接创建或者刷新的老司机们可能看不出来Sandbox Access标红提示来着,恰巧当前的org还没有使用group,所以提了case,然后得到的回复是针对Developer 以及Developer Pro sandbox,最新的release需要选择group才可以创建,这个group的人才可以使用当前的sandbox。

步骤很简单。

1. setup 搜索group然后创建一个group,group选择user需要访问当前sandbox的user。

2. sandbox 新建或者刷新最后步骤选择需要的group即可。

问题: 这个变化对我的影响大吗?这个需要确定你当前的sandbox类型以及你的操作,其实最大的变化是针对developer / developer pro的心间sandbox的变化,以下是一览图。

Sandbox 操作类型 访问Sandbox的User Group 谁可以访问Sandbox 备注
Create(Partial / Full sandbox) All User 和production user相同 Partial 以及full sandbox不严格要求创建group,可以选择All User选项,和未更新前相同
Create(所有类型) User Group User Group 当创建sandbox选择user group时,只有这些user默认active,其他user默认deactive
Refresh 和production user相同 针对dev以及dev pro sandbox,虽然我们选择了指定的group,但是刷新以后拥有production访问权限的人也可以访问这个sandbox

总结:
篇中主要介绍最新release中的sandbox创建和刷新的变化,虽然官方在 Sandbox Access标住了红色,但是报错信息前端报错导致UI没显示还是不太友好,相信很快就会修复。针对这个变化,其实对于开发人员影响有限,如果遇到同样的情况的小伙伴可以当做一个快速参考即可。篇中错误的地方欢迎指出,有不懂欢迎留言。

之前有过一系列的文章来介绍我们的
多云系统
,多云系统可以将不同云厂商不同云账号不同云区域下的资源进行集中管理,通过多云系统,用户可以像使用一朵云一样使用多云,极大的提升了多云使用的便捷性。虽然如今已经步入云时代,多云的使用十分广泛,但IDC机房仍然存在,不少企业也都还是IDC机房托管的模式,尤其是一些传统行业或是金融机构等。鉴于此,我们对多云系统进行了全面的升级,升级成了全新的资产系统,不仅可以管理多云资源,同时还能够接入IDC机房资源,实现云上云下的统一管理

资产系统针对资源的纳管提供了三种方式,分别是:多云自动同步、Agent主动上报和手工添加,这篇文章将详细介绍下这三种方式的设计、用法和区别。大概的图示如下:

这里边有个
资源池
的概念,在之前
「多云系统之资源管理」
的文章里介绍过,资源池是个虚拟的池子,用来
暂存
云上同步下来的资源,为什么说是暂存,主要是因为资源池内的资源都是没有项目归属的,属于无人认领或暂未认领的无主资源,而所有的资源最终都应该归属于项目,以便于项目维护和成本核算。由此可知,一个资源要么归属于项目,要么暂存于资源池,正常情况下所有资源都应归属到项目,那资源池也应该是空的。资源池内的资源有两种方式归属到项目,其一是通过动态规则自动处理,其二便是人工认领手动处理,这里我们当然是推荐动态规则自动处理啦,减少人为介入,一切都自动化完成

还有个
资源树节点
的概念,资源树就是我们通常所说的服务树,或者CI树,都是一样的东西,只是不同的地方叫法不一样,这里就统一叫资源树好了,资源树是一个树状结构,会有很多的节点,项目下的资源就隶属于不同的节点,通过树的方式去组织资源,可以很清晰的了解资源关系,使用起来也十分方便,应用特别广泛。就像下方图示的左侧就是一个资源树,资源归属到不同的树节点,可以根据不同的资源节点动态获取节点下的资源数据,使用起来清晰方便

有了这个基本的概念我们再来聊聊这三种资源纳管的方式

多云同步

首先是多云同步,多云同步是通过云平台的API或SDK,自动同步云资源数据到资源池,这里只需要添加云平台的账号即可,自动同步程序会根据配置的时间周期自动去云上拉取资源数据然后入库,入库时会自动判断是更新资源属性还是新建资源。新增还是更新的主要判断依据是:云账号+云区域+资源ID联合唯一,正常情况下同一云账号同一云区域下的资源ID是唯一的,所以遇到这三者一致的资源则更新,否则就新建。整体的逻辑如下图所示

动态规则自动匹配需要资源具有一定的规律性,最常用的就是通过资源名称来区别,例如一个项目A正式环境使用的Nginx服务器,我们想要给归属到项目A下的资源树节点
ProjectA/PROD/Nginx
下,那我们最好在资源命名时进行同一,例如都符合
ProjectA-PROD-Nginx-{number}
这样的格式,那动态规则就比较好创建,所有名称以
ProjectA-PROD-Nginx-
开头的资源都自动给归属到资源树节点
ProjectA/PROD/Nginx

当然资源命名没有那么规范且数量不是很多的情况下,也可以直接在资源列表页面,直接添加资源池内的资源到资源树节点下,手动添加有一个前提,那就是资源已经在资源池里了,资源到资源池里除了多云自动同步外,还有个方式那就是我们下边会讲到的Agent主动上报

多云同步比较简单,如果用到了云,那首先推荐的就是这种方式进行资源纳管,这里的云可以是公有云,例如阿里云、腾讯云、华为云等等,或者是私有云,例如VMWare、OpenStack、OpenShift等等

Agent上报

除了多云同步的方式进行资源纳管之外,我们还提供了Agent,可以部署在服务器上以实现资源主动上报统一纳管,这种方式对于IDC之类没有提供API/SDK获取资源的方式来说非常有效,通过安装客户端Agent,自动上报云资源数据,这里只需要服务器安装Agent即可,Agent只要与服务器连通,则会自动上报资源数据到资源池,然后配合动态规则自动将资源归属到资源树节点,这个过程与多云到自动纳管逻辑一致

对于安装了Agent的服务器来说,除了资源先到资源池再配合动态规则自动归属到资源树节点的管理方式外,Agent还提供直接归属到资源树节点而不经过资源池的方式。这就涉及Agent的高级配置,Agent的配置文件默认只需要配置一个Server服务器的地址即可启动,像下边这样

SERVER-ADDRESS: "wss://agent-server.ops-coffee.cn/api/v1/agent"

Agent启动之后会与Server建立连接,Server通过周期任务把Agent上报的资源入库,默认也是先进了资源池,但当Agent配置了参数
BIZ-ID
时,Server入库则首先去匹配
BIZ-ID
所对应的资源树节点是否存在,如果存在则直接给归属到资源树节点下,不存在则进资源池。这样的话,当用户明确知道自己的资源归属到哪个资源树节点时,则可以在Agent的配置文件中添加
BIZ-ID
配置,像下边这样

SERVER-ADDRESS: "wss://agent-server.ops-coffee.cn/api/v1/agent"
BIZ-ID: "37"

当Server入库数据时会先检查ID为37的资源树节点是否存在,如果存在则直接归属到节点,这样就实现了Agent直接归属到节点,而无需经过资源池的步骤,这样的话也就不需要动态规则自动匹配再归属到资源树节点了,更加方便

手动添加

以上两种方式无论是多云自动同步还是Agent主动上报,都可以实现资源的自动入库,自动归属到资源树节点,这两种方式都有一定的前提条件,要么属于云资源可以自动同步,要么安装了Agent可以主动上报,如果不属于云资源,也不想安装Agent,如何管理资源呢?那系统提供了一种最原始的方式,用户自己手工录入,或是通过Excel批量导入资源

这两种方式都比较原始,资源属性的更新依赖人工操作,不像多云同步或Agent上报,资源属性的更新都可以自动完成,所有不是特殊情况都不建议使用,这里就不多赘述了

至此,资产系统所支持的三种资源纳管理介绍完毕

一、介绍
今天是这个系列《C++之 Opencv 入门到提高》得第四篇文章。这篇文章很简单,介绍如何使用 Mat 对象来实例化图像实例,了解它的构造函数和常用的方法,这是基础,为以后的学习做好铺垫。虽然操作很简单,但是背后有很多东西需要我们深究,才能做到知其然知其所以然。OpenCV 具体的简介内容,我就不多说了,网上很多,大家可以自行脑补。
OpenCV 的官网地址:
https://opencv.org/
,组件下载地址:
https://opencv.org/releases/

OpenCV 官网学习网站:
https://docs.opencv.ac.cn/4.10.0/index.html

我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:Windows Professional 10(64位)
开发组件:OpenCV – 4.10.0
开发工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
开发语言:C++(VC16)

二、知识学习
如果我们想学习或者使用 OpenCV,它的图像对象 Mat 是绕不过去的。我们对它越熟悉,使用的越灵活。俗话说得好,欲善其事必先利其器。内容很简单,直接上代码,代码里面有注释。废话不说了。

1 #include <opencv2/opencv.hpp>
2 #include <iostream>
3 #include <math.h>
4 
5 using namespacestd;6 using namespacecv;7 
8 intmain()9 {10     //1、Mat 对象与 IplImage 对象11     //1.1、IplImage 对象是从 2001 年 OpenCV 发布之后就一直存在的,它是一个具有 C 语言风格的数据结构,需要开发者自己分配和管理内存,在大型项目中容易导致内存泄漏。12     //1.2、Mat 对象是在 OpenCV 2.0 之后才引进的图像数据结构,自动分配内存,不存在内存泄漏的问题,是下面向对象的数据结构。该 Mat 对象分成两部分,头部和数据部分。13     //1.3、 Mat 类型构造函数和常用方法。14     // 
15     //1.3.1、构造函数16     //1.3.1.1、Mat()17     //1.3.1.2、Mat(int rows, int cols, int type);18     //1.3.1.3、Mat(Size size, int type);19     //1.3.1.4、Mat(int rows, int cols, int type, const Scalar& s);20     //1.3.1.5、Mat(Size size, int type, const Scalar& s);21     //1.3.1.6、Mat(int ndims, const int* sizes, int type);22     //1.3.1.7、Mat(int ndims, const int* sizes, int type, const Scalar& s);23     //还有很多,就不列举了24     // 
25     //1.3.2、常用方法26     //1.3.2.1、void copyTo( OutputArray m ) const;27     //1.3.2.2、void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;28     //1.3.2.3、Mat clone() const; 是完全拷贝,可以得到一个全新的 Mat 对象。29     //1.3.2.4、int channels() const;30     //1.3.2.5、int depth() const;31     //1.3.2.6、bool empty() const;32     //1.3.2.7、uchar* ptr(int i0=0);33     //还有很多,就不列举了34     // 
35     // 
36     //2、Mat 对象使用37     //2.1、部分复制:一般情况下只会复制 Mat 对象的头部和指针部分,数据部分是不会复制的。38     //Mat a=imread(imagefilePath);39     //Mat b(a); 通过拷贝构造就不会数据部分。40     //2.2、完全复制:如果想把 Mat 对象的头部和数据部分一起复制,可以通过 Mat 的 clone() 或者 copyTo() 方法。41     //2.3、使用 Mat 对象的四个要点42     //A、输出图像的内存是自动分配的。43     //B、使用OpenCV的 C++ 接口,不需要考虑内存分配的问题。44     //C、赋值操作和拷贝构造函数只会复制头部和指针部分,数据部分不会复制。45     //D、使用 clone 和 copyTo 两个函数可以实现完全复制。46     // 
47     //3、Mat 定义数组48     //创建多维数据 Mat::create49     //int sz[3]={2,2,2};50     //Mat a(3,sz,CV_8UC1,Scalar::all(0));
51 
52 
53 Mat src;54     src = imread("F:\\TestImage\\ZZImage\\psb14.jpg", IMREAD_UNCHANGED);55     if(src.empty())56 {57         cout << "加载图像有错误!!" <<endl;58         return -1;59 }60 
61     namedWindow("DemoWindow", WINDOW_AUTOSIZE);62     imshow("DemoWindow", src);63 
64 
65 
66     /*Mat dst;67 dst = Mat(src.size(),src.type());68 dst = Scalar(0,255,0);*/
69 
70 
71     //Mat dst;72     //dst = Mat(src.size(), src.type());//此语句可省略73     //dst = src.clone();
74 
75     /*Mat dst;76 src.convertTo(dst,src.type());*/
77 
78     /*Mat dst;79 src.copyTo(dst);*/
80 
81     /*Mat dst;82 
83 cvtColor(src, dst, COLOR_BGR2GRAY);84 cout << "原图通道:" << src.channels() << endl;85 cout << "目标图通道:" << dst.channels() << endl;86 
87 int height = dst.rows;88 int width = dst.cols;89 cout << "行数:" << height << endl;90 cout << "列数:" << width << endl;91 
92 const uchar* firstRow = dst.ptr<uchar>(0);93 cout << "第一行的值:" << *firstRow << endl;*/
94 
95     /*Mat dst(3, 3, CV_8UC1, Scalar(0));96 
97 cout << "dst=" << endl << dst << endl;*/
98 
99     //Mat dst;100     //dst.create(src.size(),src.type());
101     ////通过 create 方法不能直接复制。
102     //dst = Scalar(0,0,255);
103 
104 
105     /*Mat dst = Mat::zeros(src.size(),src.type());*/
106 
107     Mat dst = Mat::eye(2,2,CV_8UC1);108     cout << "dst=" << endl << dst <<endl;109 
110     namedWindow("DemoWindow2", WINDOW_AUTOSIZE);111     imshow("DemoWindow2", dst);112 
113     waitKey(0);114 
115     return 0;116 }

内容很简单,就不多说了。


三、总结
这是 C++ 使用 OpenCV 的第四篇文章,其实也没那么难,感觉是不是还是很好入门的,那就继续。初见成效,继续努力。皇天不负有心人,不忘初心,继续努力,做自己喜欢做的,开心就好。


title: Nuxt.js 应用中的 prepare:types 事件钩子详解
date: 2024/11/8
updated: 2024/11/8
author:
cmdragon

excerpt:
prepare:types 钩子为 Nuxt.js 开发者提供了灵活定制 TypeScript 配置和声明的能力。通过使用此钩子,开发者能够确保 TypeScript 配置和类型声明能够满足他们的项目需求,提升代码的可维护性和类型安全性。

categories:

  • 前端开发

tags:

  • Nuxt
  • TypeScript
  • 钩子
  • 自定义
  • 类型
  • 配置
  • 构建


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

prepare:types
钩子详解

prepare:types
是 Nuxt.js 中的一个生命周期钩子,它允许开发者在 Nuxi 写入
.nuxt/tsconfig.json

.nuxt/nuxt.d.ts
文件之前,自定义 TypeScript 配置或在类型声明中添加额外的引用。这个钩子对于那些需要进行 TypeScript 定制的项目来说非常有用,使得开发者能够更好地控制和扩展 TypeScript 的类型定义。


目录

  1. 概述
  2. prepare:types 钩子的详细说明
  3. 具体使用示例
  4. 应用场景
  5. 注意事项
  6. 关键要点
  7. 总结


1. 概述

prepare:types
钩子允许开发者在 Nuxt.js 生成的 TypeScript 配置文件和声明文件被写入之前,进行自定义配置。这有助于确保在 TypeScript 项目中使用附加的类型声明或修改默认的编译配置。

2. prepare:types 钩子的详细说明

2.1 钩子的定义与作用

  • 定义
    :
    prepare:types
    是一个钩子,用于在生成 TypeScript 配置和声明文件之前调整这些文件的内容。
  • 作用
    : 开发者可以通过此钩子向生成的 TypeScript 配置 (
    tsconfig.json
    ) 和声明文件 (
    nuxt.d.ts
    ) 中注入自定义的类型声明或选项,增强类型检查和提示。

2.2 调用时机

  • 执行环境
    : 在 Nuxt 执行生成 TypeScript 配置和声明文件的过程中调用。
  • 挂载时机
    : 通常在构建过程的初始化阶段,确保开发者的自定义配置能在项目生成的相关文件中生效。

2.3 参数说明

  • options
    : 提供当前 TypeScript 配置和自定义声明的选项,开发者可以使用这些选项进行修改和扩展。

3. 具体使用示例

3.1 修改 tsconfig.json 的示例

// plugins/prepareTypes.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('prepare:types', (options) => {
    // 修改 tsconfig.json 中的编译选项
    options.tsconfig.compilerOptions.strict = true; // 开启严格模式
    options.tsconfig.include.push('my-custom-dir/**/*'); // 添加自定义目录
  });
});

在这个示例中,我们使用
prepare:types
钩子修改了
tsconfig.json
的编译选项,开启了 TypeScript 的严格模式,并添加了一个自定义目录到编译包含列表中。

3.2 在 nuxt.d.ts 中添加自定义声明的示例

// plugins/prepareTypes.js
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks('prepare:types', (options) => {
    // 在 nuxt.d.ts 中添加自定义声明
    options.declarations.push(`
      declare module 'nuxt/app' {
        interface NuxtApp {
          $myCustomProperty: string;
        }
      }
    `);
  });
});

在这个示例中,我们在
nuxt.d.ts
中添加了一个自定义声明,扩展了
NuxtApp
接口,为其添加了一个新的属性
$myCustomProperty

4. 应用场景

  1. 自定义类型声明
    : 在使用 Nuxt.js 时,可能需要添加自定义类型或接口来适配项目需求。
  2. 修改默认 TypeScript 配置
    : 通过钩子修改项目的 TypeScript 编译选项,确保符合团队或项目标准。
  3. 动态添加项目路径
    : 根据项目结构动态引入属于自定义模块或库的类型定义。

5. 注意事项

  • 兼容性
    : 确保使用的 TypeScript 特性与项目中使用的 TypeScript 版本兼容。
  • 类型冲突
    : 在添加自定义声明时,要注意避免与已有的类型冲突。
  • 性能
    : 修改
    tsconfig
    的编译选项可能会影响 TypeScript 的性能,尤其是在大型项目中。

6. 关键要点

  • prepare:types
    钩子允许开发者在生成 TypeScript 配置和声明文件之前进行自定义设置。
  • 该钩子可以帮助开发者扩展和修改 TypeScript 类型声明,以满足项目的具体需求。

7. 总结

prepare:types
钩子为 Nuxt.js 开发者提供了灵活定制 TypeScript 配置和声明的能力。通过使用此钩子,开发者能够确保 TypeScript 配置和类型声明能够满足他们的项目需求,提升代码的可维护性和类型安全性。

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
Nuxt.js 应用中的 prepare:types 事件钩子详解 | cmdragon's Blog

往期文章归档: