当前位置: 首页 > news >正文

数据仓库核心:事实表深度解析与设计指南

文章目录

    • 1. 引言
      • 1.1基本概念
      • 1.2 事实表定义
    • 2. 设计原则
      • 2.1 原则一:全面覆盖业务相关事实
      • 2.2 原则二:精选与业务过程紧密相关的事实
      • 2.3 原则三:拆分不可加事实为可加度量
      • 2.4 原则四:明确声明事实表的粒度
      • 2.5 原则五:避免同一事实表中存在不同粒度的事实
      • 2.6 原则六:统一事实的度量单位
      • 2.7 原则七:妥当处理事实的null值
      • 2.8 原则八:使用退化维度提升事实表的可用性
    • 3. 设计方法
      • 3.1 事实表设计流程
      • 3.2 设计案例:订单事实表
    • 4. 事务类型
      • 4.1 单、多事务事实表
      • 4.2 其他事实表
    • 5. 写在最后

1. 引言

1.1基本概念

事实表,顾名思义,是用来存储事实的表,这些事实通常是指可以量化的业务指标,如销售额、订单数量等。事实表的特点是有大量的行,每行代表一个业务事件的度量。

换句话说就是你要关注事物的内容,事实表就像故事中的主角,它包含我们感兴趣的主要信息(如销售金额、订购数量、利润以及它们发生的时间和地点等)。事实表中的每一行数据都代表了某种业务活动,就好比故事中的一个关键事件一样。

比如,一张记录了公司所有业务交易的清单。每一条记录都是一个事实,比如一次销售或一笔支出。

举个例子,假设我们有一个简单的销售事实表,它记录了每次销售的金额和日期:

CREATE TABLE Sales_Fact (SaleID INT PRIMARY KEY,ProductID INT,SaleAmount DECIMAL(10,2),SaleDate DATE
);

在这个例子中,SaleID 是每条销售记录的唯一标识,ProductID 与维度表相关联,SaleAmount 是销售金额,SaleDate 是销售日期。

其他详细内容可以看:数据仓库核心:揭秘事实表与维度表的角色与区别

1.2 事实表定义

事实表是数据仓库中的核心,它与维度表相对应,存储了业务过程中量化的数据,也就是我们通常所说的度量值(measures)。事实表通常包含以下主要部分:

  • 度量值:这些是事实表中的主要数据,可以进行数值计算,如销售额、订单数量、产品单价等。
  • 维度键:这些是指向维度表的外键,通过它们,事实表与维度表相连,从而为度量值提供上下文信息。
  • 上下文信息:提供额外的业务信息,如时间戳、事务ID等。

粒度”描述了事实表中每条记录所捕捉到的业务细节的深度。它可以通过两个维度来衡量:首先是维度属性的组合,它们决定了数据条目的详细程度;其次是数据条目所代表的具体业务含义。

如果我们选择“产品维度”的“SKU”作为粒度,那么我们的数据条目将非常详细,因为每个SKU都是独特的,能够反映单个商品的销售情况。例如,一个数据条目可能表示“在2024年6月10日,北京地区,某款智能手机的销售额为3000元”。

事实表中的数据,作为衡量业务流程的量度,通常以整数或小数形式出现,并分为三种可加性类型:

  • 可加性事实:这类数据允许我们在事实表的任何维度上进行汇总。例如,我们可以将不同时间或地区的销售数据进行求和,以得到总销售额。

一个销售数据的事实表,记录了每笔交易的销售额。如果我们要计算总销售额,我们可以将所有交易的销售额相加:
2024年1月1日,北京,销售额100元。
2024年1月1日,上海,销售额200元。
2024年1月2日,北京,销售额150元。

  • 半可加性事实:这类数据仅能在特定的维度上进行汇总。以库存为例,我们可以按地点或商品类别进行汇总,但如果尝试按时间维度累加每个月的库存量,这种汇总就失去了实际意义。

分析库存数据,库存数量可以按地点或商品类别进行汇总,但按时间维度累加就没有意义。例如:
2024年1月1日,北京,库存数量50件。
2024年1月1日,上海,库存数量30件。

我们可以计算北京和上海的总库存数量:80件。但如果我们尝试将1月份每天的库存数量累加,这就没有意义,因为库存数量是随时间变化的,每天的库存数量并不是独立的,而是相互关联的。

  • 不可加性事实:这类度量无法通过任何维度进行汇总,例如比率或百分比。然而,即使是不可加性事实,我们通常也可以通过将其分解为可加的组成部分,来实现某种形式的聚合分析。

记录了每个订单的利润率(销售额减去成本后的百分比),这个度量就是不可加的。例如:
订单1的利润率是20%。
订单2的利润率是15%。

我们不能简单地将这两个利润率相加得到一个总体的利润率。相反,如果我们想要得到平均利润率,我们需要先计算每个订单的实际利润,然后将这些利润相加,最后除以订单的总数。例如:
订单1的销售额是100元,成本是80元,利润是20元。
订单2的销售额是150元,成本是130元,利润是20元。
总利润是40元,订单总数是2,所以平均利润率是 40/(100+150)=16.67%

2. 设计原则

2.1 原则一:全面覆盖业务相关事实

事实表设计的核心目标是全面捕捉业务流程的每一个细节。在设计时,我们应该无一遗漏地纳入所有与业务过程紧密相关的量化事实,哪怕这可能导致数据的轻微冗余。由于事实数据通常以数字形式存储,其对存储空间的影响相对较小。

案例:在一家零售店,事实表不仅记录了每笔交易的销售额,还记录了交易时间、顾客ID和购买的商品种类。即使某些信息如交易时间在某些分析中不是必需的,它的包含仍然为更全面的业务分析提供了可能。

2.2 原则二:精选与业务过程紧密相关的事实

在挑选事实时,我们必须严格筛选,确保只包含那些直接与当前业务过程相关的事实。这有助于保持数据的清晰性和分析的准确性。

案例:在一个电商平台的订单处理过程中,事实表应记录订单号、商品详情和顾客信息,而支付金额则属于支付过程的事实,应从订单事实表中排除。

2.3 原则三:拆分不可加事实为可加度量

面对不可直接汇总的度量,我们应通过创造性地拆分,将其转化为可加的组成部分,从而扩展分析的可能性。

案例:网站的用户访问数据中,如果记录了每个页面的浏览次数和独立访客数,我们可以将“购买率”这一不可加事实拆分为“购买人数”和“浏览人数”,使得原本难以聚合的数据变得可以分析。

2.4 原则四:明确声明事实表的粒度

在设计事实表时,粒度的选择至关重要。我们应从最细的原子粒度开始设计,以满足当前和未来可能的用户需求。

案例:销售事实表可能以单个交易为粒度,记录每一次购买的详细信息。而在汇总销售数据时,我们可以按商品、时间或地区等维度进行聚合。

2.5 原则五:避免同一事实表中存在不同粒度的事实

在一张事实表中,应避免混合不同粒度的事实,以防止汇总时出现重复计算的问题。

案例:如果事实表同时记录了单个订单和包含多个子订单的大订单,那么在汇总支付金额时,大订单中的子订单金额可能会被重复计算。

小订单ID大订单ID小订单付款金额小订单购买数量大订单付款金额
L1001B11001300
L1002B12001300
L1003B21501200
L1004B2501200

2.6 原则六:统一事实的度量单位

在事实表中,所有度量单位应保持一致,无论是货币单位还是数量单位,这有助于简化分析过程并避免混淆。

案例:在财务事实表中,所有的金额数据都应该统一为“元”或“分”,确保在进行财务分析时的一致性和准确性。

2.7 原则七:妥当处理事实的null值

由于null值在某些查询中无法参与计算,我们应事先设定规则,将null值替换为零或其他适当的默认值。

案例:如果销售事实表中的“退货数量”字段出现null,我们可以将其默认填充为0,以避免在计算总销售数量时出现错误。

2.8 原则八:使用退化维度提升事实表的可用性

通过将常用维度属性直接嵌入到事实表中,我们可以减少对维度表的依赖,提高查询效率。

案例:在销售事实表中,如果将“商品名称”作为退化维度直接包含,那么在进行商品销售分析时,就无需额外关联商品维度表,从而加快查询速度并减少I/O操作。

3. 设计方法

3.1 事实表设计流程

在构建数据仓库的事实表之前,我们必须首先深入挖掘并明确业务的核心需求,以及确定事实表所扮演的角色。这一步骤要求我们对业务流程进行全面的需求分析,洞察整个业务生命周期的每一个关键步骤,并且精准筛选出与我们需求紧密相连的业务活动

接下来,我们必须精确地声明事实表的粒度,力求达到原子级别的细节,以便捕捉业务活动中最细微的变化。

在粒度确定之后,我们也随之锁定了事实表的主键。基于此,我们可以识别出与这些主键相关联的维度组合,以及它们所包含的维度字段。

我们还需要明确在这个业务过程中所度量的关键指标是什么,并确保将不可加的度量进行适当的拆分,以便于进行有效的数据聚合。

此外,为了优化查询性能和减少数据冗余,我们应该尽可能地将维度属性退化并直接嵌入到事实表中,这样不仅提升了数据的可用性,也简化了数据模型。

3.2 设计案例:订单事实表

案例背景
假设我们正在为一家电子商务公司设计一个订单事实表,该公司希望分析销售数据以优化库存管理和促销策略。

步骤1:确定业务需求和事实表的类型

  • 我们与业务团队合作,明确了需求:分析订单数据,包括销售额、订单量、顾客购买行为等。

步骤2:进行详细的需求分析

  • 我们分析了订单处理的整个生命周期,从顾客浏览商品到最终的订单交付。

步骤3:声明粒度

  • 我们选择了订单级别的原子粒度,确保每一条记录都对应一个具体的订单事件。

步骤4:确定维度

  • 基于订单粒度,我们确定了以下维度:顾客、时间、产品、促销活动等。

步骤5:确定事实

  • 我们确定了以下度量指标:订单总金额、订单中商品的总数量、退货数量等。对于不可加的度量,如退货率,我们进行了适当的转换以便聚合。

步骤6:冗余维度

  • 为了提高查询效率,我们将一些常用的维度属性,如顾客的地区信息,退化并直接包含在事实表中。
CREATE TABLE IF NOT EXISTS order_fact_table (order_id INT COMMENT '唯一标识每个订单的ID',customer_id INT COMMENT '下单顾客的ID',order_date DATE COMMENT '订单的日期',product_id INT COMMENT '订单中商品的ID',quantity INT COMMENT '订单中商品的数量',unit_price DECIMAL(10, 2) COMMENT '商品的单价,保留两位小数,确保金额的精确度',total_amount DECIMAL(15, 2) COMMENT '订单的总金额,保留两位小数,适用于大金额订单',return_quantity INT COMMENT '订单的退货数量,默认为0,表示没有退货',promotion_id INT COMMENT '订单参与的促销活动ID,如果有的话',customer_region STRING COMMENT '顾客所属的地区,使用字符串存储'
)
COMMENT '订单事实表,存储订单相关的详细数据'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
STORED AS TEXTFILE;

4. 事务类型

4.1 单、多事务事实表

单事务事实表结构简单,易于管理,适用于单一且独立的业务记录。多事务事实表则适用于复杂的业务场景,能够记录多个相关联的事务,但设计和理解上更为复杂。

特性单事务事实表多事务事实表
业务过程一个多个
粒度相互间不相关相同粒度
维度相互间不相关一致
事实只取当前业务过程中的事实保留多个业务过程中的事实,非当前业务过程中的事实需要置零处理
理解程度易于理解,不会混淆难以理解,需要通过标签来限定
计算存储成本较少,不同业务过程融合到一起,降低了存储计算量,但是非当前业务过程的度量存在大量零值较多,每个业务过程都需要计算,存储一次

4.2 其他事实表

事务事实表适用于记录具体事务的瞬间数据,周期快照事实表用于定期捕获数据状态,而累积快照事实表则追踪业务过程的完整历史,提供连续的数据视图。每种表根据其更新和加载机制,服务于不同的数据分析需求。

特性事务事实表周期快照事实表累积快照事实表
时期/时间点离散事务时间点以有规律的、可预测的时期间隔产生快照用于时间跨度不确定的不断变化的工作流
日期维度事务日期快照日期相关业务过程涉及的多个日期
粒度每行代表一个事务每行代表某时间周期的一个实体每行代表一个实体的生命周期
事实事务事实相关业务过程事实和时间间隔事实事务事实、累积事实
事实表加载插入插入插入与更新
事实表更新不更新不更新业务过程变更时更新

5. 写在最后

在本章,我们细致地构建了对事实表这一数据仓库核心元素的理解。事实表记录了企业的关键业务数据,每条记录都是业务活动的直接反映。

我们首先明确了事实表的基本功能,它集中存储了业务度量和事实,是数据分析的基础。然后,我们学习了如何根据业务需求设计事实表,挑选合适的度量,并确保通过维度键与维度表的连接,为数据分析提供必要的上下文。

我们讨论了事实表的粒度问题,这是决定我们分析细节深度的关键。我们还区分了单事务和多事务事实表,并探讨了它们在不同业务场景下的应用。

最后,我们掌握了一些高级设计原则,包括处理null值的策略、避免数据冗余的方法,以及通过退化维度来提高查询性能的技巧。这些原则有助于确保事实表的准确性和效率,支持有效的数据分析和决策制定。

随着本章的结束,希望你对事实表的设计和应用有了更清晰的认识,能够更有效地利用这一工具来挖掘数据的潜力,为企业带来价值。

相关文章:

数据仓库核心:事实表深度解析与设计指南

文章目录 1. 引言1.1基本概念1.2 事实表定义 2. 设计原则2.1 原则一:全面覆盖业务相关事实2.2 原则二:精选与业务过程紧密相关的事实2.3 原则三:拆分不可加事实为可加度量2.4 原则四:明确声明事实表的粒度2.5 原则五:避…...

Reactor和epoll

Reactor模式和epoll都是与事件驱动的网络编程相关的术语,但它们属于不同的概念层面: Reactor模式 Reactor模式是一种事件驱动的编程模型,用于处理并发的I/O事件。这种模式使用一个或多个输入源(如套接字)&#xff0c…...

Mybatis-Plus多种批量插入方案对比

背景 六月某日上线了一个日报表任务,因是第一次上线,故需要为历史所有日期都初始化一次报表数据 在执行过程中发现新增特别的慢:插入十万条左右的数据,SQL执行耗费高达三分多钟 因很早就听闻过mybatis-plus的[伪]批量新增的问题&…...

数据库面试

1. 简单介绍一下Spring中的事务管理。 答:事务就是对一系列的数据库操作(比如将insert,delete,update,select多条sql语句)作为一个整体执行,进行统一的提交或回滚操作,如果这组sql语…...

探索Web Components

title: 探索Web Components date: 2024/6/16 updated: 2024/6/16 author: cmdragon excerpt: 这篇文章介绍了Web Components技术,它允许开发者创建可复用、封装良好的自定义HTML元素,并直接在浏览器中运行,无需依赖外部库。通过组合HTML模…...

摄影师在人工智能竞赛中与机器较量并获胜

摄影师在人工智能竞赛中与机器较量并获胜 自从生成式人工智能出现以来,由来已久的人机大战显然呈现出一边倒的态势。但是有一位摄影师,一心想证明用人眼拍摄的照片是有道理的,他向算法驱动的竞争对手发起了挑战,并取得了胜利。 迈…...

CMU最新论文:机器人智慧流畅的躲避障碍物论文详细讲解

CMU华人博士生Tairan He最新论文:Agile But Safe: Learning Collision-Free High-Speed Legged Locomotion 代码开源:Code: https://github.com/LeCAR-Lab/ABS B站实际效果展示视频地址:bilibili效果地址 我会详细解读论文的内容,让我们开始吧…...

Spring中自定义注解进行类方法增强

说明 说到对类方法增强,第一时间想到自定义注解,通过aop切面进行实现。这是一种常用做法,但是在某些场景下,如开发公共组件,定义aop切面可能不是最优方案。以后通过原生aop方式,自定义注解,对类…...

TS:元组

问: 解释下什么是元组 回答: 元组(Tuple)是一种数据结构,类似于数组,但与数组不同的是,元组中的元素类型可以各不相同,且元组的长度是固定的。元组在许多编程语言中都有实现,包括 TypeScript…...

微服务 | Springboot整合Dubbo+Nacos实现RPC调用

官网:Apache Dubbo 随着互联网技术的飞速发展,越来越多的企业和开发者开始关注微服务架构。微服务架构可以将一个大型的应用拆分成多个独立、可扩展、可维护的小型服务,每个服务负责实现应用的一部分功能。这种架构方式可以提高开发效率&…...

读书的意义

...

第66集《摄大乘论》

请大家打开《讲义》第二二二页: 庚九、念(分二:辛一正念法身;辛二兼显净土) 辛一、正念法身(分二:壬一征;壬二释) 壬一、征 这个是讲到十门分别(二0三页),分别清净法身的第九段,讲到念&…...

VMware 桥接网络突然无法上网

VMware 桥接网络突然无法上网 0. 问题1. 解决方法 0. 问题 昨天,VMware 桥接网络正常使用,今天突然无法上网。 1. 解决方法 打开VMware的虚拟网络编辑器,将桥接模式的网络从“自动”改成你要使用的网卡,问题解决。 完成&#…...

面试题——Redis

★1.简述一下缓存穿透,缓存击穿,缓存雪崩 ? 缓存穿透:大量恶意请求一个不存在的数据,使得压力绕过Redis缓存层打到数据库,造成数据库瘫痪 处理:①设置黑名单,维护一个可能存在也可能不存在的黑名单数据列表,对请求进行过滤(简单高效) ②布隆过滤器,会出现误删,且相对麻烦(不…...

Java——构造器(构造方法)和 this

一、什么是构造器 构造器(Constructor)是Java类的一种特殊方法,用于初始化对象的状态。构造器在创建对象时被调用,可以对对象的成员变量进行初始化。 我之前的文章《Java——类和对象-CSDN博客》中也提到了构造器。 二、构造器…...

MySQL-连接查询

049-内连接之等值连接 案例:查询每个员工所在的部门名称,要求显示员工名、部门名。 select e.ename, d.dname from emp e inner join dept d on e.deptnod.deptno;注意:inner可以省略 select e.ename, d.dname from emp e join dept d on…...

适合小白学习的项目1832javaERP管理系统之仓库采购管理Myeclipse开发mysql数据库servlet结构java编程计算机网页项目

一、源码特点 java erp管理系统之仓库采购管理是一套完善的web设计系统,对理解JSP java编程开发语言有帮助采用了serlvet设计,系统具有完整的源代码和数据库,系统采用web模式,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Mye…...

分布式技术导论 — 探索分析从起源到现今的巅峰之旅(分布式技术)

分析探索从起源到现今的巅峰之旅 背景介绍数据可伸缩性案例 计算可伸缩性案例 代价和权衡分布式的代价分布式的权衡权衡策略 分布式技术方向数据系统运算系统 分布式数据系统Partition(分区)Round-Robin(轮询)局限性 Range&#x…...

基于Python+OpenCV+SVM车牌识别系统(GUI界面)【W3】

简介: 随着交通管理的日益复杂化和智能化需求的增加,车牌识别系统在安防、智慧交通管理等领域中扮演着重要角色。传统的车牌识别系统主要基于图像处理和模式识别技术,随着计算机视觉技术的发展,基于Python、OpenCV和机器学习算法的…...

ansible.cfg forks参数

在Ansible的配置文件ansible.cfg中,forks参数是一个非常关键的设置,它控制了Ansible执行任务时的并发连接数,直接影响到Ansible执行 playbook 或 ad-hoc 命令时的速度和效率。 意义与作用 并发控制:当你使用Ansible来管理多台主…...

Web前端写随机抽奖:技术与创意的碰撞

Web前端写随机抽奖:技术与创意的碰撞 在Web前端的世界里,随机抽奖功能不仅是一种常见的交互元素,更是技术与创意的完美结合。下面,我们将从四个方面、五个方面、六个方面和七个方面,深入探讨Web前端实现随机抽奖的技术…...

Centos系统yum安装mysql数据库

安装之前需要将系统自带的mariadb-libs软件包删除。 检查是否存在mariadb-libs包。 yum list installed|grep mariadb-libs 删除mariadb-libs包 yum -y remove mariadb-libs 声明: 系统:CentOS-7-x86_64-DVD-2009 安装为最小化安装,没…...

使用Selenium进行Web自动化:详细操作指南

使用Selenium进行Web自动化:详细操作指南 引言 Selenium是一个广泛使用的开源工具,用于自动化Web浏览器的操作。无论你是进行自动化测试,还是需要抓取网页数据,Selenium都是一个非常有用的工具。本文将详细介绍Selenium的一些常见用法,包括输入框设置值、文件上传、单选…...

手机照片免费数据恢复软件EasyRecovery2024免费版下载

大家好!今天我要给大家推荐一款非常棒的软件——EasyRecovery。相信大家都知道,电脑中的重要文件一旦丢失,对我们的工作和学习都会产生很大的影响。 而EasyRecovery软件就是专门解决这个问题的利器!它能够帮助我们快速、有效地恢…...

【工具】新手如何正确使用Pycharm?

1. 什么是JetBrains Toolbox JetBrains Toolbox是一个管理工具,用于安装、更新和管理JetBrains开发工具的所有版本。它可以简化多个IDE的管理,并确保你总是使用最新版本的软件。 2. 安装JetBrains Toolbox 步骤1:下载Toolbox 访问JetBrai…...

【JavaEE精炼宝库】多线程(6)线程池

目录 一、线程池的概念及优势 1.1 线程池的概念: 1.2 线程池的优势: 二、工厂模式 三、标准库中的线程池 3.1 标准库线程池参数解释: 3.1.1 corePoolSize | maximumPoolSize: 3.1.2 keepAliveTime | unit: 3.1…...

数据仓库和数据库的区别

数据仓库和数据库在许多方面存在显著的区别,主要体现在数据的用途、架构、设计原则和性能优化上。以下是两者之间的详细区别: 1. 目的和用途 数据库(Database): 主要用途:用于日常业务操作和事务处理。数据…...

芯片验证分享7 —— 代码审查1

大家好,我是谷公子,前几节课给大家分享了如何设计激励,今天我们来如何进行代码审查。 之前讨论的是基于计算机的验证技术,现在讨论非基于计算机的验证过程(即“人工验证”,或代码审查)。代码审查在查找错误方面非常有…...

Shell脚本从入门到实战

一、概述 shell 是一个命令行解释器,它接受应用程序、用户命令,然后调用操作系统内核。 shell 还是一个功能强大编程语言,易调试,易编写,灵活性强。 二、mac 怎么重启docker 1.如何重启 Docker on Mac 在 macOS 上…...

使用 python 将 Markdown 文件转换为 ppt演示文稿

在这篇博客中,我们将展示如何使用 wxPython 创建一个简单的图形用户界面 (GUI),以将 Markdown 文件转换为 PowerPoint 演示文稿。我们将利用 markdown2 模块将 Markdown 转换为 HTML,并使用 python-pptx 模块将 HTML 内容转换为 PowerPoint 幻…...

上海网站建设与设计公司好/申请百度收录网址

一、问题引入 维护老项目&#xff0c;看到下面一个函数&#xff1a; /// <summary>/// 从ViewState中获取某个属性的值。如果该属性不存在&#xff0c;返回空字符串。/// </summary>/// <param name"PropertyName">属性名称</param>/// <…...

做网站如何适应分辨率/google网站入口

module cpuMod(interface b); enum {read, write} instr; logic [7:0] raddr; always @(posedge b.clk) if (instr...

wordpress获取当前页地址/b2b电子商务平台排名

原题地址 题目描述 有 nnn 个小朋友坐成一圈&#xff0c;每人有 aia_iai​ 个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为 111 。 输入格式 小朋友个数 nnn&#xff0c;下面 nnn 行 aia_iai​ 。 输出格式 求使所有人获得均等糖果的最小代价。 输入输出…...

做旅行的网站/正规网站优化推广

近日没啥事情&#xff0c;研究了一下 everything、光速搜索原理。花了一个礼拜时间&#xff0c;终于搞定。 废话不多说&#xff0c;直接上代码&#xff1a; [delphi] view plaincopy unit uMFTSearchFile; { dbyoungsina.com 2018-04-23 } interface uses Windows, …...

阿里云虚拟主机做企业网站/谷歌优化培训

SLF4J与Logback简介 Java日志框架众多&#xff0c;常用的有java.util.logging, log4j, logback&#xff0c;commons-logging等。 SLF4J (Simple Logging Facade For Java)&#xff0c;它是一个针对于各类Java日志框架的统一Facade抽象。SLF4J定义了统一的日志抽象接口&#xff…...

韩国什么网站是专做皮草的/宁波seo推广哪家好

先看一下效果 选择分组 选择服务器 开始链接 为什么写 之前写过一个字符界面的链接工具&#xff0c;但是看起来比较简陋&#xff0c;他是这个样子的&#xff1a; 看起来十分不好看。后来在网上看到shell中有一个whiptail工具可以制作各种命令行里的工具&#xff0c;于是就搜索了…...