设计模式学习笔记 - 面向对象 - 9.实践:如何进行面向对象分析、设计与编码

1.如何对接口鉴权这样一个功能开发做面向对象分析

本章会结合一个真实的案例,从基础的需求分析、职责划分、类的定义、交互、组装运行讲起,将最基础的面向对象分析(00A)、设计(00D)、编程(00P)的套路讲清楚,为后面的设计原则和设计模型打好基础。

1.1 案例介绍和难点剖析

假设,你参与开发一个微服务。微服务通过 HTTP 暴露接口给其他系统调用。有一天,你的领导找到你说,“为了保证接口调用的安全,希望设计实现一个接口调用鉴权功能,只有经过认证的系统才能调用微服务接口,没有认证过的系统会被拒绝。希望由你来开发,争取尽管上线”。

这个时候,你可能会有脑子里一团浆糊,一时间无从下手的感觉? 有这种感觉的原因,个人觉得有以下两点。

1.需求不明确

领导给的需求过于模糊、笼统,离落地到设计、编码还有一定的距离。而人的大脑不擅长思考这种过于抽象的问题。

前面讲过,面向对象分析主要的分析对象是“需求”。因此,面向对象分析可以粗略地看成“需求分析”。实际上,不管是需求分析还是面向对象分析,首先要做的是将笼统的需求细化到足够清晰、可执行。需要通过沟通、挖掘、分析、假设、梳理,搞清楚具体的需求有哪些,哪些是现在要做,哪些是未来可能要做的,哪些是不用考虑的。

2.缺少锻炼

相比单纯的 CRUD 开发,鉴权这个开发任务更有难度。鉴权作为一个根具体业务无关的功能,完全可以把它独立开发成一个独立的框架,集成到很多业务系统中。而作为被很多系统复用的通用框架,比如普通的代码,我们对框架的代码质量要求更高。

开发这样的通用框架,对工程师的需求分析能力、设计能力、编码能力,甚至逻辑思维能力的要求,都是比较高的。如果你平时做的都是简单的 CRUD 业务开发,那这方面的锻炼肯定不会很多,所以,一旦遇到这种开发需求,很容易因缺少锻炼,脑子放空,不知道从何入手,完全没有思路。

1.2 对案例进行需求分析

实际上,需求分析的工作很琐碎,没有固定的方法论。系统通过这个例子,给你展示下需求分析时,完整的考虑思路是什么样的。希望你自己体会,举一反三地应用到其他项目的需求分析中。

针对鉴权这个功能的开发,该如何做需求分析?

实际上,这和做算法题类似,先从最简单的法案想起,然后再优化。所以,我把分析的过程分为了循序渐进的四轮。

第一轮基础分析

对于如何鉴权这样的问题,最简单的解决方案是,通过用户名加密码来做认证。我们给每个允许访问服务的调用方,派发一个 APPID 和一个对应的密码。调用方每次请求时都携带自己的 APPID 和密码。微服务在接受到接口调用请求后,会解析出 APPID 和密码,和存储的 APPID 和密码进行对比。如果一致则允许调用请求;否则拒绝调用。

第二轮分析优化

这样的验证方式,每次都要传输明文密码。密码很容易被屏蔽,是不安全的。那如果借助加密算法(比如 SHA),对密码进行加密后,再传递到微服务端验证,是不是就可以了?

实际上这样也不安全,因为加密之后的密码及 APPID,照样可以被未认证系统(或黑客)截获,未认证系统可以携带这个加密之后的面以及对应的 APPID,伪装成已认证系统来访问我们的接口。这就是典型的“重放攻击”。

提出问题,再解决问题,是一个非常好的迭代方式。对于刚刚的问题,可以借助 OAuth 的验证思路来解决。 调用方将请求的 URLAPPID、密码拼接在一起,然后进行加密,生成一个 token 。调用方在接口请求的时候,将这个 tokenAPPID,跟着 URL 一块传递给服务端。服务端接受到这些数据后,根据 APPID 从数据库中取出对应的密码,并通过同样的 token 生成算法,生成另外一个 token。用这个新生成的 token 和调用方传递过来的 token 对比。如果一致,则允许接口调用请求;否则拒绝调用。

客户端过程

1.生成token SHA(http://www.test.com/user?id=123&appid=abc&pwd=def)
2.生成新URLhttp://www.test.com/user?id=123&appid=abc&pwd=def&token=xxx

服务端过程

3.解析出 URL、Appid、token
4.从数据库中根据 Appid 取出 pwd
5.使用同样的算法生成服务端 token_s
6. token == token_s,允许访问;token != token_s,拒绝访问。

第三轮分析优化

经过第二轮优化后,仍然存在重放攻击的风险。因为每个 URL 拼接上 Appid 、密码生成的 token 都是固定的。

为解决这个问题,可以进一步优化 token 生成算法,引入一个随机变量,让每次接口请求生成的 token 都不一样。可以选择时间戳作为随机变量。现在使用 URLAppid、密码、时间戳四种进行加密生成 token。调用方在进行接口请求时,将 tokenAppid、时间戳,随着 URL 一起传给微服务端。

微服务端在接受到这些数据后,会验证当前时间戳和传递过来的时间戳,是否在一定的时间窗口内(如一分钟)。如果超过时间窗口,则判定 token 过期,拒绝接口请求。如果没有超过时间窗口,则说明 token 没有过期,就在通过同样的 token 生成算法,在服务端生成新的 token,和调用方的 token 对比。若一致,则允许接口调用请求;否则,拒绝调用。

优化后的认证流程如下

客户端流程:

1.生成token SHA(http://www.test.com/user?id=123&appid=abc&pwd=def&ts=156152345)
2.生成新URLhttp://www.test.com/user?id=123&appid=abc&pwd=def&token=xxx&ts=156152345

服务端流程:

3.解析出 URL、Appid、token
4.验证token是否失效。失效就拒绝访问,否则执行5
5.从数据库中根据 Appid 取出 pwd
6.使用同样的算法生成服务端 token_s
7. token == token_s,允许访问;token != token_s,拒绝访问。

第四轮分析优化

不过,你可能会说,这样还是不够安全呀。未认证系统还是可以在一分钟的 token 失效窗口内,通过截取请求,来调用我们的借口OA。

你说的不错。不过在攻与防之间,本来就没有绝对的安全。我们能做的就是,尽量提高攻击的成本。这个方案虽然还有漏洞,但是实现起来足够简单,而且不会过度影响接口本身的性能(比如响应时间)。

实际上,还有一个细节我们还没有考虑到,那就是,如何在微服务端存储每个授权调用方的 Appid 和密码。当然,这个问题并不难。最容易想到的方案就是存储到数据库里,比如 MySQL。不过,像开发这样的非业务功能,最好不要与具体的第三方系统有过度的耦合。

针对 Appid 和密码的存储,最后可以灵活支持不同的存储方式,比如 Zookeeper、本地配置文件、自研配置中心、MySQLRedis 等。我们不一定针对每种存储都去做实现,但起码要留有扩展点,保证系统足够的灵活性和扩展性,能在我们切换存储方式的时候,尽可能少的改动代码。

最终确定需求

  • 调用方进行接口请求的时候,将 URLAppid、密码、时间戳拼接在一起,通过加密算法生成 token,并将 tokenAppid、时间戳拼接在 URL 中,一并发送到微服务端。
  • 微服务端在接收到调用方的请求后,从请求中解析出 tokenAppid、时间戳。
  • 微服务端首先检查传递过来的时间戳是否在 token 失效时间窗口内。若已失效,则接口调用鉴权失败,拒绝接口调用请求
  • 如果 token 没有过期失效,微服务再从自己的存储中,取出 Appid 对应的密码,通过同样的 token 生成算法,生成另一个 token,与调用方的 token 进行比对。如果一致,则鉴权成功,允许接口调用;否则就拒绝接口调用。

这就是我们的需求分析的整个过程,从最粗糙、最模型的需求开始,通过“提出问题 - 再解决问题”的方式,循序渐进的方式进行优化,最后得到一个足够清晰、可落地的需求描述。

2.如何利用面向对象设计和编程开发接口鉴权功能?

2.1如何进行面向对象设计(OOD)

面向对象分析的产出是详细的需求描述,面向对象设计的产出是类。在面向对象设计环节,我们将需求描述转化成具体的类的设计。

设计这一环节拆解细化,主要包含以下几个部分:

  • 划分职责而识别出有哪些类
  • 定义类及其属性和方法
  • 定义类之间的交互关系
  • 将类组装起来并提供执行入口

划分职责而识别出有哪些类

根据需求描述,把其中涉及的功能点,一个个罗列出来,然后再去看看哪些功能职责相近,操作同样的属性,是否应该归为同一个类。

我们来看下,针对鉴权这个例子,具体如何来做。之前我们依据确定了最终需求,如下:

  • 调用方进行接口请求的时候,将 URLAppid、密码、时间戳拼接在一起,通过加密算法生成 token,并将 tokenAppid、时间戳拼接在 URL 中,一并发送到微服务端。
  • 微服务端在接收到调用方的请求后,从请求中解析出 tokenAppid、时间戳。
  • 微服务端首先检查传递过来的时间戳是否在 token 失效时间窗口内。若已失效,则接口调用鉴权失败,拒绝接口调用请求
  • 如果 token 没有过期失效,微服务再从自己的存储中,取出 Appid 对应的密码,通过同样的
    token 生成算法,生成另一个 token,与调用方的 token 进行比对。如果一致,则鉴权成功,允许接口调用;否则就拒绝接口调用。

首先是逐字逐句地阅读上面的需求,拆解成一个个小的功能点,一条条罗列下来。注意,拆解出来的每个功能点要尽可能小。每个功能点只负责一个很小的事情(专业叫法是“单一职责”)。下面是逐句拆解下来后,得到的功能点罗列:

  1. URLAppid、密码、时间戳拼接为一个字符串
  2. 对字符串通过加密算法加密得到 token
  3. tokenAppid、时间戳拼接在 URL 中,形成新的 URL
  4. 解析得到 tokenAppid、时间戳等信息
  5. 根据时间戳判断 token 是否过期失效
  6. 从存储中取出 Appid 对应的密码
  7. 验证两个 token 是否匹配

从上面的功能列表中,我们发现 1、2、5、7 都是和 token 相关,负责 token 的生成、验证。3、4 都是在处理 URL,负责 URL 的拼接和解析;6 是操作 Appid 和密码,负责从存储中读取 Appid 和密码。所以,我们可以粗略地得到三个核心类:AuthTokenURLCredentialStorage

  • AuthToken 负责 1、2、5、7 这四个操作。
  • URL 负责 3、4 这两个操作。
  • CredentialStorage 负责 6 这个操作。

当然,这是一个初步的类划分,其他一些不重要的类,我们可能暂时没有办法一下子想全,但这也没关系,面向对象分析、设计、编程本来就是一个循环迭代、不断优化的过程。根据需求,我们先给出一个粗糙版本的设计方案,然后基于这样一个基础,再去迭代优化,会更加容易些,思路也更加清晰一些。

需要强调一点,接口调用鉴权这个需求比较简单,所以需求对应的面向对象设计并不复杂,识别出来的类也不多。如果是面向的更加大型的软件开发、更加复杂的需求,涉及的功能点可能会很多,对应的类也会比较多,像刚刚那样根据需求逐句罗列功能点的方法,最后会得到一个很长的列表,就会优点凌乱、没有规律。

针对这种复杂的需求开发,首先要做的是进行模块划分,将需求先简单划分成几个小的、独立的功能模块,然后再在模块内部,应用刚刚的方法,进行面向对象设计。而模块的划分和识别,跟类的划分和识别,是类似的套路。

定义类及其属性和方法

通过刚刚的需求分析,识别出了三个核心类:AuthTokenURLCredentialStorage。现在再来看下,每个类有哪些属性和方法。我们还是从功能点列表中挖掘。

AuthToken 类相关的功能点有四个:
  • URLAppid、密码、时间戳拼接为一个字符串
  • 对字符串通过加密算法加密得到 token
  • 根据时间戳判断 token 是否过期失效
  • 验证两个 token 是否匹配

对于方法的识别,一般都是识别需求描述中的动词,作为候选方法,再进一步过滤筛选。类比下方法的识别,可以把功能点中涉及的名词,作为候选属性,然后同样进行过滤筛选。

借用这个思路,识别出 AuthToken 类的属性和方法

/****** AuthToken ******/
// 属性
private static final long DEFAULT_EXPIRED_TIME_INTERVAL = 60000;
private String token;
private long createTime;
private long expiredTimeInterval = DEFAULT_EXPIRED_TIME_INTERVAL;// 构造函数
public AuthToken(String token, long createTime);
public AuthToken(String token, long createTime, long expiredTimeInterval);// 函数
public static AuthToken create(String baseUrl, long createTime, Map<String, String> params;)
public String getToken();
public boolean isExpired();
public boolean match(AuthToken authToken);

从上面的类中,我们可以返现这样三个小细节:

  • 第一个细节: 并不是所有出现的名词都被定义为类的属性,比如 URLAppid、密码、时间戳这几个名词,我们把它作为了方法的参数。
  • 第二个细节:我们还需要挖掘出一些没有出现在功能点描述中的属性,比如 createTimeexpiredTimeInterval,它们用在 isExpired() 函数中用来判断 token 是否过期。
  • 第三个细节:我们还给 AuthToken 类添加了一个功能点描述中没有提到的方法 getToken()

第一个细节高速我们,从业务模型上来说,不应该属于这个类的属性和方法,不应该被放到这个类中。比如 URLAppid 这些信息,从业务模型上来说,不应该属于 AuthToken ,所以不应该放到这个类中。

第二、第三个细节高速我们,在设计类具体有哪些属性和方法的时候,不能单纯地依赖当下的需求,还要分析这个类从业务模型上来讲,应该具有哪些属性和方法。这样一方面保证类定义的完整性,另一方面不仅为当下的需求,还为未来的需求做些准备。

Url 类相关的功能点有两个
  • tokenAppid、时间戳拼接在 URL 中,形成新的 URL
  • 解析得到 tokenAppid、时间戳等信息

虽然需求描述中,都是以 URL 来代指接口请求,但是,接口请求并不一定是 URL 的形式来表达,还可能是 DubboRPC 等其他形式。为了让这个类设计的更加通用,命名更加贴切,我们接下来把它命名为 ApiRequest。下面是根据功能点描述设计的 ApiRequest

/****** ApiRequest ******/// 属性
private String baseUrl;
private String token;
private String appId;
private long timestamp;// 构造函数
public ApiRequest(String baseUrl, String token,String appId, long timestamp);// 函数
public static ApiRequest createFromUrl(String url);public String getBaseUrl();
public String getToken();
public String getAppId();
public long getTimestamp();
CredentialStorage 类相关的功能点有一个
  • 从存储中取出 Appid 对应的密码
    CredentialStorage 类很简单。为了做到抽象封装具体的存储方式,我们将 CredentialStorage 设计成了接口,基于接口而非实现编程。
/****** CredentialStorage ******/// 接口函数
String getPasswordByAppId(String appId);

定义类之间的交互关系

类与类之间的关系有哪些? UML 统一建模语言定义了 6 种类之间的关系。分别是:泛化、实现、关联、聚合、组合、依赖。

泛化可以简单理解为继承关系。

public class A {...}
public class B extends A {...}

实现一般是指接口和实现类之间的关系。

public interface A {...}
public class B implements A {...}

聚合 是一种包含关系,A 类对象包含 B 类对象,B 类对象的生命周期可以不依赖 A 类对的生命周期,也就是说可以单独销毁 A 类对象而不影响 B 类对象,比如课程与学生的关系。

public class A {private B b;public A(B b) {this.b = b;}
}

组合也是一种包含的关系。A 类对象包含 B 类对象,B 类对象的生命周期依赖 A 类对的生命周期,B 类对象不可以单独存在,比如鸟与翅膀的关系。

public class A {private B b;public A() {this.b = new B();}
}

关联 是一种比较弱的关系,包含组合和聚合。如果 B 类对象是 A 类的成员变量,那 B 类和 A 类就是关联关系。

public class A {private B b;public A(B b) {this.b = b;}
}或者public class A {private B b;public A() {this.b = new B();}
}

依赖是一种比关联关系更加弱的关系,包含关联关系。不管 B 类对象是 A 类的成员变量,还是 A 类的方法使用 B 类对象作为入参、返回值、局部变量,只要 B 类对象和 A 类对象有任何使用关系,都称它们具有依赖关系。

public class A {private B b;public A(B b) {this.b = b;}
}或者public class A {private B b;public A() {this.b = new B();}
}或者public class A {public void func(B b) {...}
}

个人觉得这样拆分的太细,增加了学习的成本,对指导编程没有太大的意义。所以,我只保留了四个关系:泛化、实现、组合、依赖。其中泛化、实现、依赖的定义不变,组合关系替代 UML 中的组合、聚合、关联这三个概念。

相当于重命名关联关系为组合关系,且不在区分组合和聚合这两个概念。
只要 B 类对象,是 A 类的成员变量,那就成 A 类和 B 类具有组合关系。

在看下我们定义的类之间有哪些关系?因为目前只有三个核心类,所以只用到了实现关系,即 CredentialStorageMySqlCredentialStorage 之间是实现关系。接下来讲到组装类的时候,还会用到依赖关系、组合关系,但是泛化关系暂时没有用到。

将类组装起来并提供执行入口

类定义好了,类之间的泛化关系也设计好了,接下来我们要将所有的类组装在一起,提供一个执行入口。这个入口可能是 main() 函数,也可能是一组给外部用的 API 接口。通过这个入口,我们能触发整个代码跑起来。

接口鉴权并不是一个独立运行的系统,而是一个集成在系统上运行的组件,所以,我们封装所有的实现细节,设计一个最顶层的 ApiAuthenticator 接口类,暴露一组给外部调用者或者 API 接口,作为触发执行鉴权逻辑的入口。

/****** ApiAuthenticator ******/
// 接口函数
void auth(String url);
void auth(ApiRequest apiRequest);

实现类

/****** DefaultApiAuthenticatorImpl ******/
// 属性
private CredentialStorage credentialStorage;
// 构造函数
public DefaultApiAuthenticatorImpl();
public DefaultApiAuthenticatorImpl(CredentialStorage credentialStorage);
// 函数
void auth(String url);
void auth(ApiRequest apiRequest);

2.2 如何进行面向对象编程(OOP)

面向对象设计完成之后,已经定义了清晰的类、属性、方法、类之间的交互,并将所有的类组装起来,提供了统一的执行入口。接下来,面向对象编程的工作,就是将这些设计思路翻译成代码实现。有了前面分析,这部分工作相对来说就比较简单了。所以,这里,只给出比较复杂的 ApiAuthenticator 的实现。

对于 AuthTokenApiRequestCredentialStorage 这三个类,就不给出具体代码实现了。你可以自己试着把整个鉴权框架自己实现一遍。

public interface ApiAuthenticator {void auth(String url);void auth(ApiRequest apiRequest);
}public class DefaultApiAuthenticatorImpl implements ApiAuthenticator {private CredentialStorage credentialStorage;public DefaultApiAuthenticatorImpl() {this.credentialStorage = new MysqlCredentialStorage();}public DefaultApiAuthenticatorImpl(CredentialStorage credentialStorage) {this.credentialStorage = credentialStorage;}@Overridevoid auth(String url) {ApiRequest apiRequest = ApiRequest.createFromUrl(url);auth(apiRequest);}@Overridevoid auth(ApiRequest apiRequest) {String appId = apiRequest.getAppId();String token = apiRequest.getToken();long timestamp = apiRequest.getTimestamp();String baseUrl = apiRequest.getBaseUrl();AuthToken clientAuthToken = new AuthToken(token, timestamp);if(clientAuthToken.isExpired()) {throw new RuntimeException("Token is exipred.");}String password = credentialStorage.getPasswordByAppId(appId);AuthToken serverAuthToken = AuthToken.generator(baseUrl, appId, password, timestamp);if(!serverAuthToken.match(clientAuthToken)) {throw new RuntimeException("Token verfication failed.");}}
}

2.3 辩证思考与灵活应用

之前讲解过,面向对象分析、设计、编程,每个环节的界限划分都比较清楚。而且,设计和实现基本上是按照功能点的描述,逐句照着翻译过来的。这样做的好处是先做什么,后做什么,都非常清晰、明确。

不过在平时的工作中,大部分程序员往往都是在脑子里或者草纸上完成面向对象分析和设计后,然后就开始写了,边写边思考重构,并不会严格地按照刚刚的流程来执行。而且,说实话,即使在写代码之前,花很多时间做分析和设计,绘制出完美的类图、UML 图,也不可能把每个细节、交互都想的很清楚。在落实到代码的时候,还是要反复迭代、重构、打破重写。

毕竟,整个软件开发本来就是一个迭代、修修补补、遇到问题解决问题的过程,是一个不断重构的过程。我们没法严格地按照顺序执行各个步骤。

2.4 总结回顾

面向对象分析的产出是详细的需求描述。面向对象设计的产出是类。在面向对象设计这一环节,我们将需求描述转化为具体的类的设计。这个环节的工作可以分为四步:

  1. 划分职责进而识别出有哪些类
    根据需求描述,把其中涉及的功能点,一个个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为一个类。
  2. 定义类的属性和方法
    识别出功需求中的动词,作为候选方法,再进一步过滤筛选出真正的方法;把功能点中涉及的名词,作为候选属性,然后再同样进行过滤筛选。
  3. 定义类与类之间的关系
    UML 统一建模语言定义了六种类之间的关系。分别是:泛化、实现、关联、组合、聚合、依赖。从贴近编程的角度,我们对类之间的关系做了调整,保留四个关系:泛化、实现、组合、依赖。
  4. 将类封装起来并提供执行入口
    将所有类组装在一起,提供一个执行入口。这个入口可能是 main() 函数,也可能是一组给外部调用的 API 接口。通过这个接口,我们能触发整个代码跑起来。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《opencv实用探索·二十二》支持向量机SVM用法

1、概述 在了解支持向量机SVM用法之前先了解一些概念&#xff1a; &#xff08;1&#xff09;线性可分和线性不可分 如果在一个二维空间有一堆样本&#xff0c;如下图所示&#xff0c;如果能找到一条线把这两类样本分开至线的两侧&#xff0c;那么这个样本集就是线性可分&#…

GIS之深度学习03:Anaconda无法正常启动问题汇总(更新)

在安装完成anaconda后&#xff0c;总会出现一些问题&#xff0c;以下为遇到的问题及解决方案&#xff1a; &#xff08;有问题请私信&#xff0c;持续更新&#xff09; 01&#xff1a;anaconda navigator启动时一直卡在 loading applications 页面 解决&#xff1a; 找到anac…

水库安全监测方案(福建地区水库安全监测案例分享)

我司星创易联最近在福建省受到了一个水库安全监测系统项目的委托。该水库位于福建中部山区,作为该地区的重要防洪与供水工程,对下游数十万人的生活产生重大影响。但是因为水库附近地质情况复杂,水库大坝在多次洪水冲击下出现一定病害,亟须全面加强对水库大坝安全状况的监测,以确…

使用Docker部署Nacos集群和Nginx高可用负载(9节点集群部署)

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容部署Nacos集群Nginx高可用负载 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专…

力扣5. 最长回文子串(双指针、动态规划)

Problem: 5. 最长回文子串 文章目录 题目描述思路复杂度Code 题目描述 思路 思路1&#xff1a;双指针 1.我们利用双指针从中间向两边扩散来判断是否为回文串&#xff0c;则关键是找到以s[i]为中心的回文串&#xff1b; 2.我们编写一个函数string palindrome(string &s, in…

复盘昨天的内容

vue调节css 后端做业务处理 1.分类管理 GetMapping("/queryCtc")public ApiResult queryCtc(){return ctcService.queryCtc();}/*** 修改类目信息* return*/PutMapping("/updateCtc")public ApiResult updateCtc(RequestBody ShopCtc shopCtc){return c…

【论文阅读】基于人工智能目标检测与跟踪技术的过冷流沸腾气泡特征提取

Bubble feature extraction in subcooled flow boiling using AI-based object detection and tracking techniques 基于人工智能目标检测与跟踪技术的过冷流沸腾气泡特征提取 期刊信息&#xff1a;International Journal of Heat and Mass Transfer 2024 级别&#xff1a;EI检…

硬件监控:使用ipmitool实现Linux系统下对服务器硬件监控

一、监控背景 运维人员对服务器硬件监控主要通过主机人员日常巡检&#xff0c;以及zabbix监控。zabbix监控仅限于服务器CPU、内存故障等导致宕机或者重启从而触发主机状态告警&#xff0c;单个磁盘挂载文件系统因坏盘导致文件系统读写异常&#xff0c;其他情况下zabbix很难监控…

Vue3.0+vue-router 实现权限路由方案一

定义路由 const routes[{path: /page,name: "dashboard",redirect: /page/home,meta: {title: "首页",},component: () > import(/components/layout/index.vue),children: [{path: home,name: "home",meta: {title: "仪表盘",ro…

MongoDB之MongoDBConnectorBI安装与使用

MongoDB之MongoDBConnectorBI安装与使用 文章目录 MongoDB之MongoDBConnectorBI安装与使用1. What is the MongoDB Connector for BI?1. 官网2. 是什么&#xff1f;3. 主要功能和作用4. 应用用场景 2. 安装及说明1. 前提条件2. 在Windows下的安装3.启动与运行1. 配置2. 启动mo…

计算机网络:路由协议

路由协议简介 路由协议是计算机网络中不可或缺的一部分&#xff0c;它们负责确定数据包从源地址到目的地址的最佳路径。想象一下&#xff0c;如果你是一个数据包&#xff0c;路由协议就像是地图或导航工具&#xff0c;指导你如何到达目的地。 目录 路由协议简介 工作原理简化…

开源大模型LLM大爆发,数据竞赛已开启!如何使用FuseLLM实现大语言模型的知识融合?

开源大模型LLM大爆发&#xff0c;数据竞赛已开启&#xff01;如何使用FuseLLM实现大语言模型的知识融合&#xff1f; 现在大多数人都知道LLM是什么&#xff0c;以及可以做什么。 人们讨论着它的优缺点&#xff0c;畅想着它的未来&#xff0c; 向往着真正的AGI&#xff0c;又有…

prometheus告警

alter 告警 下载解压安装 wget https://github.com/prometheus/alertmanager/releases/download/v0.26.0/alertmanager-0.26.0.linux-amd64.tar.gz tar -zxvf alertmanager-0.26.0.linux-amd64.tar.gz -C /usr/local mv alertmanager-0.26.0.linux-amd64 alertmanager配置起启…

Vue插件之Plugins

插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。 通过 app.component() 和 app.directive() 注册一到多个全局组件或自定义指令。 通过 app.provide() 使一个资源可被注入进整个应用。 向 app.config.globalProperties 中添加一些全局实例属性或方法 一个可能上述三种…

Element UI组件安装使用会了吗?

Element UI 是一套基于 Vue.js 构建的开源 UI 组件库&#xff0c;它提供了丰富且易于使用的 UI 组件&#xff0c;能够帮助开发者快速搭建企业级的页面。以下是详细的 Element UI 组件安装及使用步骤&#xff1a; 安装 Element UI 方法一&#xff1a;使用 npm 安装 适用于 No…

Qt程序设计-仪表盘自定义控件实例

本文讲解Qt仪器表盘自定义控件实例。 效果如下 创建仪表表盘类 #ifndef DIALPLATE_H #define DIALPLATE_H#include <QWidget> #include <QTimer> #include <QPainter> #include <QPen> #include <QDebug> #include <QtMath> #include &l…

程序员就业数据分析,需要掌握python哪些知识?

程序员就业数据分析是一项综合性的工作&#xff0c;涉及到多个方面的知识和技能。Python 是一种功能强大的编程语言&#xff0c;被广泛应用于数据分析领域。要进行程序员就业数据分析&#xff0c;你需要掌握一系列与 Python 相关的知识。 1. Python 基础知识 基本语法和数据类…

序列化-反序列化--json-xml-protoBuf

序列化和反序列化 数据在网络中传输需要按照一定的规范组成。这些规定的规范有json,xml,protobuf。 序列化 也就是说数据需要通过网络传输时&#xff0c;需要把数据转化为需要的传输格式&#xff0c;所以需要把需要传输的数据生成json或者xml或者protobuf语言格式文件&#…

【论文阅读】基于图像处理和卷积神经网络的板式换热器气泡识别与跟踪

Bubble recognizing and tracking in a plate heat exchanger by using image processing and convolutional neural network 基于图像处理和卷积神经网络的板式换热器气泡识别与跟踪 期刊信息&#xff1a;International Journal of Multiphase Flow 2021 期刊级别&#xff1a;…

IDEA中 @SpringBootApplication 多个注解无法引入依赖

终于解决了&#xff01;&#xff01;&#xff01; cd到报红项目的根目录&#xff0c;然后输入mvn idea:idea就行了。