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

设计模式学习笔记 - 面向对象 - 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 接口。通过这个接口,我们能触发整个代码跑起来。

相关文章:

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

1.如何对接口鉴权这样一个功能开发做面向对象分析 本章会结合一个真实的案例&#xff0c;从基础的需求分析、职责划分、类的定义、交互、组装运行讲起&#xff0c;将最基础的面向对象分析&#xff08;00A&#xff09;、设计&#xff08;00D&#xff09;、编程&#xff08;00P&…...

【iOS ARKit】RealityKit 同步机制

协作 Session 可以很方便地实现多用户之间的AR体验实时共享&#xff0c;但开发者需要自行负责并确保AR场景的完整性&#xff0c;自行负责虚拟物体的创建与销毁。为简化同步操作&#xff0c;RealityKit 内建了同步机制&#xff0c;RealityKit 同步机制基于 Multipeer Connectivi…...

【数据结构与算法】整数二分

问题描述 对一个排好序的数组&#xff0c;要求找到大于等于7的最小位置和小于等于7的最大位置 大于等于7的最小位置 易知从某个点开始到最右边的边界都满足条件&#xff0c;我们要找到这个区域的最左边的点。 开始二分&#xff01; left指针指向最左边界&#xff0c;right…...

java项目打包运行报异常:xxxxx-1.0-SNAPSHOT.jar中没有主清单属性

pom.xml中加入这段话即可 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.4</version><executions><execution><…...

MAC-键盘command快捷键、设置windows快捷键

在 Windows PC 专用键盘上&#xff0c;请用 Alt 键代替 Option 键&#xff0c;用 Ctrl 键或 Windows 标志键代替 Command 键。 Mac 键盘快捷键 - 官方 Apple 支持 (中国) 设置windows快捷键 使用mac外接适用于windows的键盘时&#xff0c;如何设置快捷键&#xff1f;_mac外…...

C++ 补充之常用遍历算法

C遍历算法和原理 C标准库提供了丰富的遍历算法&#xff0c;涵盖了各种不同的功能。以下是一些常见的C遍历算法以及它们的概念和原理的简要讲解&#xff1a; for_each&#xff1a;对容器中的每个元素应用指定的函数。 概念&#xff1a;对于给定的容器和一个可调用对象&#xff…...

【Linux杂货铺】调试工具gdb的使用

目录 &#x1f308;前言&#x1f308; &#x1f4c1;背景介绍 &#x1f4c1; 使用 list [行号] / [函数名] run/r break/b [行号] / [函数名] info break disable break enable break delete break [断点编号] next/n step/s continue/c finish print/p [变量…...

FL Studio Producer Edition2024中文进阶版Win/Mac

FL Studio Producer Edition&#xff0c;特别是其【中文进阶版 Win/Mac】&#xff0c;是数字音乐制作领域中的一款知名软件。它为广大音乐制作人、声音工程师以及音乐爱好者提供了一个从音乐构思到最终作品发布的完整解决方案。这个版本特别为中文用户优化&#xff0c;并兼容W…...

无需邀请码,Xinstall实现精准分享归因

在如今的移动互联网时代&#xff0c;分享已经成为了我们日常生活中不可或缺的一部分。无论是社交媒体上的好友分享&#xff0c;还是应用内的内容分享&#xff0c;分享都能够帮助我们快速传播信息&#xff0c;扩大影响力。然而&#xff0c;对于开发者而言&#xff0c;分享却带来…...

机器人与AGI会撞出什么火花?

真正的科技变革是不是就要来临了&#xff1f;各方大佬都开始布局机器人&#xff0c;对于普通人的就业会造成什么影响&#xff1f; ​ 优牛企讯-企业动态信息监控专家 在优牛企讯-企业动态监控专家搜索可知&#xff0c;全国目前的机器人公司已经达到了26401家&#xff0c;近一年…...

Linux yum安装pgsql出现Bad GPG signature错误

官方文档&#xff1a;https://www.postgresql.org/download/linux/redhat/ sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm sudo yum install -y postgresql12-server sudo /usr/pgsql-12/bin/…...

第18章-DHCP

1. 产生背景 2. 概述 2.1 定义 2.2 特点 2.3 DHCP系统组成 3. DHCP工作原理 3.1 前提条件 3.2 场景 3.3 分配IP地址工作机制 3.4 特殊情况处理 3.5 IP地址租约更新 4. DHCP中继代理 4.1 现实场景 4.2 工作机制 1. 产生背景 现实问题&#xff1a; 小型网络中&…...

[物联网] OneNet 多协议TCP透传

[物联网] OneNet 多协议TCP透传 STM32物联网–ONENET云平台的多协议接入产品创建 : https://blog.csdn.net/qq_44942724/article/details/134492924 Onenet tcp 透传 : https://blog.csdn.net/flyme2010/article/details/107086001 tcp服务端测试工具 : http://tcp.xnkiot.com/…...

如何让网页APP化 渐进式Web应用(PWA)

前言 大家上网应该发现有的网页说可以安装对应应用&#xff0c;结果这个应用好像就是个web&#xff0c;不像是应用&#xff0c;因为这里采用了PWA相关技术。 PWA&#xff0c;全称为渐进式Web应用&#xff08;Progressive Web Apps&#xff09;&#xff0c;是一种可以提供类似…...

50 vmalloc 的实现

前言 这里说的是 内核中分配按页分配的场景 常用于 驱动什么的, 分配 中大型空间 由于 连续的 n 个页是分别使用 alloc_pages 分配的, 因此是 虚拟地址空间连续, 但是 物理地址空间不连续 如何分配对象 两个步骤, __get_vm_area_node 获取为 size 分配的 vma 区间, 然后…...

程序员的金三银四求职宝典!

目录 ​编辑 程序员的金三银四求职宝典 一、为什么金三银四是程序员求职的黄金时期&#xff1f; 二、如何准备金三银四求职&#xff1f; 1. 完善简历 2. 增强技术能力 3. 提前考虑目标公司 4. 提前准备面试 三、程序员求职的常见面试题 1. 数据结构和算法 2. 数据库 …...

day04_拦截器Apifox角色管理(登录校验,API接口文档,权限管理说明,角色管理,添加角色,修改角色,删除角色)

文章目录 1. 登录校验1.1 需求说明1.2 实现思路1.3 ThreadLocal1.4 AuthContextUtil1.5 拦截器使用1.5.1 拦截器开发1.5.2 拦截器注册 1.6 代码优化1.6.1 配置优化1.6.2 代码优化1.6.3 前端修改 2. API接口文档2.1 Apifox接口管理平台2.1.1 接口管理平台简介2.1.2 Apifox简介2.…...

在线上传解压PHP文件代码,压缩/压缩(网站一键打包)支持密码登录

在线上传解压PHP文件代码&#xff0c;压缩/压缩(网站一键打包)支持密码登录 资源宝分享&#xff1a;www.httple.net 如果你没有主机控制面板这个是最好选择&#xff0c;不需要数据库&#xff0c;上传当控制面板使用&#xff0c;无需安装任何扩展&#xff0c;安全高&#xff0c;…...

【刷题】模拟

模拟算法&#xff1a;题目中已经告诉应该怎么做了&#xff0c;只需要模拟即可&#xff0c;思路比较简单&#xff0c;比较考察代码能力。 一般先在草稿纸上模拟流程&#xff0c;如果直接写代码&#xff0c;容易忽视细节&#xff0c;并且不容器调试&#xff01; 优化策略&#…...

【打工日常】使用docker部署在线Photopea用于linux下替代ps

一、Photopea介绍 linux没有ps适配&#xff0c;对于有时候工作来说确实不方便&#xff0c;我找了很久&#xff0c;才找到了一款功能可以跟ps接近的在线软件&#xff0c;使用docker部署就可以了。它是ps的最佳替代品之一&#xff0c;其界面几乎与ps相同&#xff0c;只不过它是在…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

DiscuzX3.5发帖json api

参考文章&#xff1a;PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下&#xff0c;适配我自己的需求 有一个站点存在多个采集站&#xff0c;我想通过主站拿标题&#xff0c;采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁

赛门铁克威胁猎手团队最新报告披露&#xff0c;数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据&#xff0c;严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能&#xff0c;但SEMR…...

从零开始了解数据采集(二十八)——制造业数字孪生

近年来&#xff0c;我国的工业领域正经历一场前所未有的数字化变革&#xff0c;从“双碳目标”到工业互联网平台的推广&#xff0c;国家政策和市场需求共同推动了制造业的升级。在这场变革中&#xff0c;数字孪生技术成为备受关注的关键工具&#xff0c;它不仅让企业“看见”设…...

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...