系统设计
2024-03-18 12:32:27 0 举报
AI智能生成
系统设计
作者其他创作
大纲/内容
基础知识
RESTful API
API
API(Application Programming Interface) 翻译过来是应用程序编程接口的意思。
把 API 理解为程序与程序之间通信的桥梁,其本质就是一个函数而已。
定义
RESTful API 经常也被叫做 REST API,它是基于 REST 构建的 API。
REST
REST 的全称是 Resource Representational State Transfer ,直白翻译就是 “资源”在网络传输中以某种“表现形式”进行“状态转移” 。
资源(Resource):我们可以把真实的对象数据称为资源。一个资源既可以是一个集合,也可以是单个个体。
表现形式(Representational):"资源"是一种信息实体,它可以有多种外在表现形式。
状态转移(State Transfer):REST 中的状态转移更多地描述的服务器端资源的状态。
RESTful 架构
每一个 URI 代表一种资源。
客户端和服务器之间,传递这种资源的某种表现形式比如 json,xml,image,txt 等等。
客户端通过特定的 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"。
动作
GET:请求从服务器获取特定资源。举个例子:GET /classes(获取所有班级)
POST:在服务器上创建一个新的资源。举个例子:POST /classes(创建班级)
PUT:更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /classes/12(更新编号为 12 的班级)
DELETE:从服务器删除特定的资源。举个例子:DELETE /classes/12(删除编号为 12 的班级)
PATCH:更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。
路径(接口命名)
路径又称"终点"(endpoint),表示 API 的具体网址。
规范
网址中不能有动词,只能有名词,API 中的名词也应该使用复数。
不用大写字母,建议用中杠 - 不用下杠 _ 。
善用版本化 API。
接口尽量使用名词,避免使用动词。
过滤信息(Filtering)
如果我们在查询的时候需要添加特定条件的话,建议使用 url 参数的形式。
状态码(Status Codes)
2xx:成功
200 成功
201 创建
3xx:重定向
301 永久重定向
304 资源未修改
4xx:客户端错误
400 错误请求
401 未授权
403 禁止访问
404 未找到
405 请求方法不对
5xx:服务器错误
500 服务器错误
502 网关错误
504 网关超时
HATEOAS
返回结果中提供链接,连向其他 API 方法,使得用户不查文档,也知道下一步应该做什么。即 Hypermedia API 设计,也被称为 HATEOAS。
软件工程
在更少资源消耗的情况下,创造出更好、更容易维护的软件。
开发过程
软件开发过程(software development process)或软件过程(software process)是软件开发的开发生命周期(software development life cycle),
其各个阶段实现了软件的需求定义与分析、设计、实现、测试、交付和维护。软件过程是在开发与构建系统时应遵循的步骤,是软件开发的路线图。
其各个阶段实现了软件的需求定义与分析、设计、实现、测试、交付和维护。软件过程是在开发与构建系统时应遵循的步骤,是软件开发的路线图。
需求分析:分析用户的需求,建立逻辑模型。
软件设计:根据需求分析的结果对软件架构进行设计。
编码:编写程序运行的源代码。
测试 : 确定测试用例,编写测试报告。
交付:将做好的软件交付给客户。
维护:对软件进行维护比如解决 bug,完善功能。
开发模型
瀑布模型(Waterfall Model)、快速原型模型(Rapid Prototype Model)、V 模型(V-model)、W 模型(W-model)、敏捷开发模型。
敏捷开发核心
持续集成、重构、小版本发布、低文档、站会、结对编程、测试驱动开发。
基本策略
软件复用
通过复用已有的一些轮子(框架、第三方库等)、设计模式、设计原则等等现成的物料,可以更快地构建出一个满足要求的软件。
分而治之
将一些比较复杂的问题拆解为一些小问题,然后,一一攻克。
在领域驱动(Domain Driven Design,简称 DDD)设计中,很重要的一个概念就是领域(Domain),它就是我们要解决的问题。
在领域驱动设计中,我们要做的就是把比较大的领域(问题)拆解为若干的小领域(子域)。
在领域驱动设计中,我们要做的就是把比较大的领域(问题)拆解为若干的小领域(子域)。
逐步演进
软件开发是一个逐步演进的过程,我们需要不断进行迭代式增量开发,最终交付符合客户价值的产品。
MVP(Minimum Viable Product,最小可行产品),刚好能够满足客户需求的产品。
优化折中
软件开发是一个不断优化改进的过程,要学会折中,在有限的投入内,以最有效的方式提高现有软件的质量。
代码命名
好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。
规范
驼峰命名法(CamelCase)
使用大小写混合的格式来区别各个单词,并且单词之间不使用空格隔开或者连接字符连接的命名方式。
大驼峰命名法(UpperCamelCase)
类名
小驼峰命名法(lowerCamelCase)
方法名、参数名、成员变量、局部变量
蛇形命名法(snake_case)
各个单词之间通过下划线“_”连接。
优点:命名所需要的单词比较多时,更易读。
应用:测试方法名、常量、枚举名称。
串式命名法(kebab-case)
各个单词之间通过连接符“-”连接。
应用:项目文件夹名称。
基本命名规范
类名需要使用大驼峰命名法(UpperCamelCase)风格。方法名、参数名、成员变量、局部变量需要使用小驼峰命名法(lowerCamelCase)。
测试方法名、常量、枚举名称需要使用蛇形命名法(snake_case)。并且,测试方法名称要求全部小写,常量以及枚举名称需要全部大写。
项目文件夹名称使用串式命名法(kebab-case),比如dubbo-registry。
包名统一使用小写,尽量使用单个名词作为包名,各个单词通过 "." 分隔符连接,并且各个单词必须为单数。
抽象类命名使用 Abstract 开头。
异常类命名使用 Exception 结尾。
测试类命名以它要测试的类的名称开始,以 Test 结尾。
POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
命名易读性规范
为了能让命名更加易懂和易读,尽量不要缩写/简写单词,除非这些单词已经被公认可以被这样缩写/简写。
命名不像函数一样要尽量追求短,可读性强的名字优先于简短的名字,虽然可读性强的名字会比较长一点。
避免无意义的命名,你起的每一个名字都要能表明意思。
避免命名过长(50 个字符以内最好),过长的命名难以阅读并且丑陋。
不要使用拼音,更不要使用中文。
代码重构
定义
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
利用设计模式(如组合模式、策略模式、责任链模式)、
软件设计原则(如 SOLID 原则、YAGNI 原则、KISS 原则)和重构手段(如封装、继承、构建测试体系)来让代码更容易理解,更易于修改。
软件设计原则(如 SOLID 原则、YAGNI 原则、KISS 原则)和重构手段(如封装、继承、构建测试体系)来让代码更容易理解,更易于修改。
核心
步子一定要小,每一步的重构都不会影响软件的正常运行,可以随时停止重构。
常见的设计模式
单例模式
⼀种确保⼀个类只有⼀个实例,并提供⼀个全局访问点来访问该实例的创建模式。
原理
⼀个私有构造函数(确保只能单例类⾃⼰创建实例):单例类通常会将其构造函数设为私有,以防⽌外部代码直接实例化对象。
⼀个私有静态变量(确保只有⼀个实例):单例类通常包含⼀个私有的静态变量,⽤于保存该类的唯⼀实例。
⼀个公有静态函数(给使⽤者提供调⽤⽅法)。
优点
使⽤单例模式就可以避免⼀个全局使⽤的类,频繁的创建与销毁,耗费系统资源。
分类
懒汉式(线程不安全)
先不创建实例,当第⼀次被调⽤时,再创建实例,所以被称为懒汉式。
优点
延迟了实例化,如果不需要使⽤该类,就不会被实例化,只有在需要时才创建实例,避免了资源浪费。
缺点
线程不安全,多线程环境下会实例化多个实例。
饿汉式(线程安全)
先不管需不需要使⽤这个实例,直接先实例化好实例(饿死⻤⼀样,所以称为饿汉式),然后当需要使⽤的时候,直接调⽅法就可以使⽤了。
优点
提起实例化好了⼀个实例,避免了线程不安全问题的出现。
缺点
直接实例化了实例,不再延迟实例化。若系统没有使⽤这个实例,或者系统运⾏很久之后才需要使⽤这个实例,都会使操作系统的资源浪费。
懒汉式(线程安全)
实现和线程不安全的懒汉式⼏乎⼀样,唯⼀不同的点是,在 get ⽅法上加了⼀把锁(synchronized)。
优点
延迟实例化,节约了资源,并且是线程安全的。
缺点
虽然解决了线程安全问题,但是因为有锁导致性能降低了。
双重检查锁实现(线程安全)
双重检查锁相当于是改进了线程安全的懒汉式。获取锁之前增加空判断,并且增加 volatile 关键字修饰静态实例。
volatile 作用
禁⽌ JVM 的指令重排,可以保证多线程环境下的安全运⾏。
优点
延迟实例化,节约了资源;线程安全;并且相对于线程安全的懒汉式,性能提⾼了。
缺点
volatile 关键字,对性能也有⼀些影响。
静态内部类实现(线程安全)
新增静态内部类持有实例,外部类提供静态方法返回内部类持有的实例。仅在获取实例时,才会被初始化,⽽且 JVM 会确保只被实例化⼀次。
优点
延迟实例化,节约了资源,且线程安全,性能也提⾼了。
枚举类实现(线程安全)
默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。
优点
写法简单,线程安全,天然防⽌反射和反序列化调⽤。
序列化
把 java 对象转换为字节序列的过程。
反序列化
通过这些字节序列在内存中新建 java 对象的过程。
将⼀个单例实例对象写到磁盘再读回来,从⽽获得了⼀个新的实例。枚举类天然防⽌反序列化。
其他单例模式 可以通过 重写 readResolve() ⽅法,从⽽防⽌反序列化,使实例唯⼀重写。
应用
资源共享:当多个模块或系统需要共享某⼀资源时,可以使⽤单例模式确保该资源只被创建⼀次,避免重复创建和浪费资源。
控制资源访问:单例模式可以⽤于控制对特定资源的访问,例如数据库连接池、线程池等。
配置管理器:当整个应⽤程序需要共享⼀些配置信息时,可以使⽤单例模式将配置信息存储在单例类中,⽅便全局访问和管理。
⽇志记录器:单例模式可以⽤于创建⼀个全局的⽇志记录器,⽤于记录系统中的⽇志信息。
线程池:在多线程环境下,使⽤单例模式管理线程池,确保线程池只被创建⼀次,提⾼线程池的利⽤率。
缓存:单例模式可以⽤于实现缓存系统,确保缓存只有⼀个实例,避免数据不⼀致性和内存浪费。
⼯⼚模式
定义
⼯⼚模式是⼀种⾮常常⽤的创建型设计模式,其提供了创建对象的最佳⽅式。
在创建对象时,不会对客户端暴露对象的创建逻辑,⽽是通过使⽤共同的接⼝来创建对象。
原理
⽤来封装和管理类的创建,本质是对获取对象过程的抽象。
优点
解耦:将对象的创建和使⽤进⾏分离,客户端代码与具体产品类的实例化过程解耦,客户端只需知道⼯⼚和抽象产品的接⼝,⽽不需要关⼼具体的实现类。
可复⽤:对于创建过程⽐较复杂且在很多地⽅都使⽤到的对象,通过⼯⼚模式可以提⾼对象创建的代码的复⽤性。
易于扩展:添加新的产品类时,只需要扩展相应的具体产品和具体⼯⼚,⽽不需要修改已有的代码,符合开闭原则。
更符合⾯向对象的设计原则:通过⼯⼚模式,将对象的创建封装在⼯⼚类中,使得系统更符合单⼀职责原则。
分类
简单⼯⼚
定义
简单⼯⼚模式也被称为静态⼯⼚⽅法模式。
简单⼯⼚模式并不是⼀个标准的设计模式,更像是⼀种编程习惯。
在简单⼯⼚模式中,⼀个⼯⼚类负责创建多个产品类的实例,通过传⼊不同的参数来决定创建哪种产品。
同时在简单⼯⼚模式中会定义⼀个类负责创建其他类的实例,被创建的实例也通常具有共同的⽗类。
缺点
虽实现了对象的创建和使⽤的分离,但不够灵活,⼯⼚类集合了所有产品的创建逻辑,职责过重。
新增⼀个产品就需要在原⼯⼚类内部添加⼀个分⽀,违反了开闭原则。
若是有多个判断条件共同决定创建对象,则后期修改会越来越复杂。
应用
JDK 中的 DateFormate、Calendar 类都有使⽤,通过不同参数返回我们需要的对象。
⼯⼚⽅法
定义
将简单⼯⼚中的⼯⼚类变为⼀个抽象接⼝。
负责给出不同⼯⼚应该实现的⽅法,⾃身不再负责创建各种产品,⽽是将具体的创建操作交给实现该接⼝的⼦⼯⼚类来做。
优点
通过多态的形式解决了简单⼯⼚模式过多的分⽀问题。
虽然在新增产品时不仅要新增⼀个产品类还要实现与之对应的⼦⼯⼚,但是相较于简单⼯⼚模式更符合开闭原则。
应用
JDK 中的 Collection 接⼝中 Iterator 的实现。
抽象⼯⼚
定义
提供⼀个创建⼀系列相关或相互依赖对象的接⼝,⽽⽆需指定它们的具体类。
通常涉及多个抽象产品、多个具体产品和多个具体⼯⼚。
缺点
虽然对于新增⼀个产品族很方便,并且也符合开闭原则,但是新增⼀个产品等级结构,会对整个⼯⼚结构进⾏⼤改。
应用
Spring 中的 BeanFactory。
条件
当⼀个类不知道它所需要的类的时候。
当⼀个类希望通过其⼦类来指定创建对象的时候。
当类将创建对象的职责委托给多个帮助⼦类中的某⼀个,并且希望将哪⼀个帮助⼦类是代理者的信息局部化时。
应用
在数据库操作中,通过⼯⼚模式可以根据不同的数据库类型(MySQL、Oracle等)创建对应的数据库连接对象。
通过⼯⼚模式可以根据配置⽂件或其他条件选择不同类型的⽇志记录器,如⽂件⽇志记录器、数据库⽇志记录器等。
在图形⽤户界⾯(GUI)库中,可以使⽤⼯⼚模式创建不同⻛格或主题的界⾯元素,如按钮、⽂本框等。
在加密算法库中,可以使⽤⼯⼚模式根据需要选择不同的加密算法,例如对称加密、⾮对称加密等。
在⽂件解析过程中,可以使⽤⼯⼚模式根据⽂件类型选择不同的解析器,如XML解析器、JSON解析器等。
在⽹络通信库中,可以使⽤⼯⼚模式创建不同类型的⽹络连接对象,如TCP连接、UDP连接等。
观察者模式
定义
属于⾏为型模式的⼀种,它定义了⼀种⼀对多的依赖关系,让多个观察者对象同时监听某⼀个主题对象。
这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够⾃动更新⾃⼰。
构成
Subject(主题)
主题是被观察的对象,它包含了⼀组观察者对象,并提供了添加、删除和通知观察者的⽅法。主题通常有⼀个状态,当状态改变时,通知所有观察者。
Observer(观察者)
观察者是依赖于主题的对象,当主题的状态发⽣改变时,观察者得到通知并进⾏相应的更新。观察者的具体实现类需要实现更新的⽅法。
ConcreteSubject(具体主题)
具体主题是主题的具体实现类,它维护了⼀个状态,并在状态改变时通知观察者。
ConcreteObserver(具体观察者)
具体观察者是观察者的具体实现类,实现了更新的⽅法,以便在接收到通知时进⾏相应的处理。
优点
解除耦合,让耦合的双⽅都依赖于抽象,从⽽使得各⾃的变换都不会影响另⼀边的变换。
缺点
调试复杂,⽽且在Java中消息的通知⼀般是顺序执⾏,那么⼀个观察者卡顿,会影响整体的执⾏效率,在这种情况下,⼀般会采⽤异步实现。
实现
定义主题接⼝(Subject),包括注册、移除和通知观察者的⽅法。
定义观察者接⼝(Observer),包括更新的⽅法。
创建具体主题类(Concrete Subject),维护⼀组观察者对象,并在状态改变时通知观察者。
创建具体观察者类(Concrete Observer),实现更新的⽅法。
客户端代码中创建主题对象和观察者对象,注册观察者到主题中,然后通过主题改变状态,观察者得到通知并进⾏更新。
应用
事件处理:当⼀个对象的状态发⽣改变时,观察者模式可以⽤于通知和处理与该对象相关的事件。
这在图形⽤户界⾯(GUI)开发中是很常⻅的,例如按钮点击事件、⿏标移动事件等。
这在图形⽤户界⾯(GUI)开发中是很常⻅的,例如按钮点击事件、⿏标移动事件等。
发布订阅:观察者模式可以⽤于实现发布-订阅模型,其中⼀个主题(发布者)负责发送通知,
⽽多个观察者(订阅者)监听并响应这些通知。这种模型在消息队列系统、事件总线等场景中经常使⽤。
⽽多个观察者(订阅者)监听并响应这些通知。这种模型在消息队列系统、事件总线等场景中经常使⽤。
MVC架构:观察者模式常被⽤于实现MVC架构中的模型和视图之间的通信。
当模型的状态发⽣改变时,所有相关的视图都会得到通知并更新显示。
当模型的状态发⽣改变时,所有相关的视图都会得到通知并更新显示。
异步编程:观察者模式可以⽤于处理异步任务的完成事件。任务完成时,通知所有相关的观察者进⾏后续处理。
代理模式
定义
代理模式(Proxy Pattern)是⼀种结构型设计模式,其主要⽬的是在访问某个对象时引⼊⼀种代理对象,通过代理对象控制对原始对象的访问。
结构
抽象主题(Subject):定义了代理对象和真实对象的共同接⼝,使得代理对象能够替代真实对象。
真实主题(Real Subject):是实际执⾏业务逻辑的对象,是代理模式中的被代理对象。
代理(Proxy):包含⼀个指向真实主题的引⽤,提供与真实主题相同的接⼝,可以控制对真实主题的访问,并在需要时负责创建或删除真实主题的实例。
作用
实现懒加载、控制访问、监控对象等场景。
策略模式
定义
策略模式(Strategy Pattern)是⼀种⾏为设计模式,它定义了⼀系列算法,把它们单独封装起来并可以互相替换,使算法独⽴于使⽤它的客户端⽽变化。
这些算法所完成的功能类型是⼀样的,对外接⼝也是⼀样的,只是不同的策略为引起环境⻆⾊表现出不同的⾏为。
相⽐于使⽤⼤量的if...else,使⽤策略模式可以降低复杂度,使得代码更容易维护。
构成
策略接⼝(Stragety): 策略接⼝定义了算法的抽象,具体的策略类实现了这个接⼝。
具体策略类(ConcreteStragety): 具体策略类实现了策略接⼝,封装了具体的算法。
环境类(Context):⽤来操作策略的上下⽂环境类,环境类的构造函数包含了 Strategy 类,
通过多态传进来不同的具体策略(ConcreteStrategyA。ConcreteStrategyB)来调⽤不同策略的⽅法。
通过多态传进来不同的具体策略(ConcreteStrategyA。ConcreteStrategyB)来调⽤不同策略的⽅法。
实现
定义⼀个策略接⼝,声明算法的抽象⽅法。
创建具体的策略类,实现策略接⼝,封装具体的算法。
创建环境类,包含对策略接⼝的引⽤,以及⼀个⽤于设置具体策略对象的⽅法。
在客户端中创建环境类的对象,并调⽤其⽅法来执⾏具体的算法。
优点
使⽤策略模式可以避免使⽤多重条件转移语句。多重转移语句将算法或⾏为的逻辑混合在⼀起,不易维护。
缺点
可能需要定义⼤量的策略类,并且这些策略类都要提供给客户端。
客户端必须知道所有的策略类,并⾃⾏决定使⽤哪⼀个策略类,策略模式只适⽤于客户端知道所有的算法或⾏为的情况。
应用
个商场销售系统根据不同的促销策略计算最终价格。
装饰模式
定义
装饰模式(Decorator Pattern)是⼀种结构型设计模式,它允许在不改变原始类接⼝的情况下,动态地添加功能或责任。
装饰模式通过创建⼀个装饰类,包裹原始类的实例,并在保持原始类接⼝不变的情况下,提供额外的功能。就增加功能来说,装饰模式⽐⽣成⼦类更为灵活。
结构
组件接⼝:定义了具体组件和装饰器共同的接⼝,确保它们可以互相替换。
具体组件:实现了组件接⼝,是被装饰的具体对象。
装饰器:持有⼀个组件对象的引⽤,并实现了组件接⼝。装饰器通常是⼀个抽象类,它的具体⼦类实现具体的装饰逻辑。
具体装饰器:继承⾃装饰器,实现了具体的装饰逻辑,并调⽤⽗类的⽅法以保持接⼝⼀致。
应用
装饰模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。
因此,当需要执⾏特殊⾏为时,客户代码就可以在运⾏时根据需要有选择地、按顺序地使⽤装饰功能包装对象了。
因此,当需要执⾏特殊⾏为时,客户代码就可以在运⾏时根据需要有选择地、按顺序地使⽤装饰功能包装对象了。
23种设计模式
创建型模式
⼯⼚⽅法(factory method)模式
定义⼀个创建对象的接⼝,但由⼦类决定需要实例化哪⼀个类。⼯⼚⽅法使得⼦类实例化的过程推迟。
抽象⼯⼚(abstract factory)模式
提供⼀个接⼝,可以创建⼀系列相关或相互依赖的对象,⽽⽆需指定他们具体的类。
原型(prototype)模式
⽤原型实例指定创建对象的类型,并且通过拷⻉这个原型来创建新的对象。
单例(singleton)模式
保证⼀个类只有⼀个实例,并提供⼀个访问它的全局访问点。
构建器(builder)模式
将⼀个复杂类的表示与其构造相分离,使得相同的构建过程能够得出不同的表示。
结构型模式
适配器(adapter)模式
将⼀个类的接⼝转换成⽤户希望得到的另⼀个接⼝。它使原本不相容的接⼝得以协同⼯作——速记关键字:转换接⼝。
桥接(bridge)模式
将类的抽象部分和它的实现部分分离开来,使它们可以独⽴地变化——速记关键字:继承树拆分。
组合(composite)模式
将对象组合成树型结构以表示“整体-部分”的层次结构,使得⽤户对单个对象和组合对象的使⽤具有⼀致性——速记关键字:树形⽬录结构。
装饰(decorator)模式
动态地给⼀个对象添加⼀些额外的职责。它提供了⽤⼦类扩展功能的⼀个灵活的替代,⽐派⽣⼀个⼦类更加灵活——速记关键字:附加职责。
外观(facade)模式
定义⼀个⾼层接⼝,为⼦系统中的⼀组接⼝提供⼀个⼀致的外观,从⽽简化了该⼦系统的使⽤——速记关键字:对外统⼀接⼝。
享元(flyweight)模式
提供⽀持⼤量细粒度对象共享的有效⽅法。
代理(proxy)模式
为其他对象提供⼀种代理以控制这个对象的访问。
⾏为型模式
职责链(chain of responsibility)模式
通过给多个对象处理请求的机会,减少请求的发送者与接收者之间的耦合。
将接收对象链接起来,在链中传递请求,直到有⼀个对象处理这个请求——速记关键字:传递职责。
将接收对象链接起来,在链中传递请求,直到有⼀个对象处理这个请求——速记关键字:传递职责。
命令(command)模式
将⼀个请求封装为⼀个对象,从⽽可⽤不同的请求对客户进⾏参数化,将请求排队或记录请求⽇志,⽀持可撤销的操作——速记关键字:⽇志记录,可撤销。
解释器(interpreter)模式
给定⼀种语⾔,定义它的⽂法表示,并定义⼀个解释器,该解释器⽤来根据⽂法表示来解释语⾔中的句⼦。
迭代器(iterator)模式
提供⼀种⽅法来顺序访问⼀个聚合对象中的各个元素⽽不需要暴露该对象的内部表示。
中介者(mediator)模式
⽤⼀个中介对象来封装⼀系列的对象交互。
它使各对象不需要显式地相互调⽤,从⽽达到低耦合,还可以独⽴地改变对象间的交互——速记关键字:不直接引⽤。
它使各对象不需要显式地相互调⽤,从⽽达到低耦合,还可以独⽴地改变对象间的交互——速记关键字:不直接引⽤。
备忘录(memento)模式
在不破坏封装性的前提下,捕获⼀个对象的内部状态,并在该对象之外保存这个状态,从⽽可⽤在以后将该对象恢复到原先保存的状态。
观察者(observer)模式
定义对象间的⼀种⼀对多的依赖关系,当⼀个对象的状态发⽣改变时,所有依赖于它的对象都得到通知并⾃动更新。
状态(state)模式
允许⼀个对象在其内部状态改变时改变它的⾏为——速记关键字:状态变成类。
策略(strategy)模式
定义⼀系列算法,把它们⼀个个封装起来,并且使它们之间可互相替换,从⽽让算法可以独⽴于使⽤它的⽤户⽽变化。
模板⽅法(template method)模式
定义⼀个操作中的算法⻣架,⽽将⼀些步骤延迟到⼦类中,使得⼦类可以不改变⼀个算法的结构即可重新定义算法的某些特定步骤。
访问者(visitor)模式
表示⼀个作⽤于某对象结构中的各元素的操作,使得在不改变各元素的类的前提下定义作⽤于这些元素的新操作。
常见的软件设计原则
单⼀职责
在设计类的时候要尽量缩⼩粒度,使功能明确、单⼀,不要做多余的事情(⾼内聚,低耦合)。
开闭原则
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着可以通过扩展来添加新功能,⽽不必修改现有代码。
⾥⽒替换
⼦类型必须能够替换掉它们的基类型。在程序中,如果有⼀个基类和⼀个⼦类,那么可以⽤⼦类对象替换基类对象,⽽程序的⾏为仍然是正确的。
接⼝隔离
不应该强迫⼀个类实现它不需要的接⼝。⼀个类不应该对它⽤不到的⽅法负责。
依赖倒置
⾼层模块不应该依赖于低层模块,⽽是应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
⽐如Java的操作数据库,Java定义了⼀组接⼝,由各个数据库去实现它,Java不依赖于他们,数据库依赖于Java。
⽐如Java的操作数据库,Java定义了⼀组接⼝,由各个数据库去实现它,Java不依赖于他们,数据库依赖于Java。
组合优于继承
继承耦合度⾼,组合耦合度低。继承基类是固定好的,但是组合通过组合类的指针,可以传⼊不同的类,避免⾼耦合。
组合复⽤原则
概念
优先使⽤组合 contains a(聚合 has a),⽽不是继承 is a 来达到⽬的。
原因
继承会将实现细节暴露给⼦类,继承复⽤破坏了封装性,是⽩箱复⽤。使⽤继承时需要考虑⾥⽒替换原则。
优点
新类对象存取成员对象只通过成员对象的接⼝,是⿊箱复⽤,系统更灵活,降低耦合度。
可以在运⾏时动态进⾏,新对象可动态引⽤与成员对象类型相同的对象。
可以在运⾏时动态进⾏,新对象可动态引⽤与成员对象类型相同的对象。
缺点
需要管理较多对象。
迪⽶特法则
概念
⼀个对象应当对其他对象有尽可能少的了解,即不和陌⽣⼈说话,“朋友圈”概念。
this
该对象⽅法中的参数。
实例变量直接引⽤的对象。
实例变量如果是⼀个聚集(聚合对象),聚集中的元素。
该对象⽅法中创建的变量。
要求
优先考虑将⼀个类设计成不变类。
尽量降低⼀个类的访问权限。
谨慎使⽤ Serializable(持久化,通过序列化⼀个对象,将其写⼊磁盘,以后程序调⽤时重新恢复该对象)。
尽量降低成员的访问权限。
优点
降低类之间的耦合。
缺点
过多使⽤迪⽶特法则,会产⽣⼤量中介类,设计变复杂。
YAGNI(你不需要它原则)
You Aren't Gonna Need It 的缩写。
极限编程原则告诫开发人员,他们应该只实现当前所需的功能,并避免实现未来需要的功能,仅在必要时才实现。
遵守这一原则可以减小代码库大小,同时避免时间和生产力浪费在没有价值的功能上。
DRY(不要重复你自己原则)
系统中,每一块知识都必须是单一、明确而权威的。
DRY 是 Do not Repeat Yourself 的缩写。这个原则旨在帮助开发人员减少代码的重复性,并将公共代码保存在一个地方。
与 DRY 相反的是 WET(功能实现两次或者喜欢打字 Write Everything Twice or We Enjoy Typing)。
实际上,如果你在两个或更多的地方有相同的功能,你可以使用 DRY 原则将它们合并为一个,并在任何你需要的地方重复使用。
KISS 原则
保持简单和直白。
KISS 原则指明了如果大多数的系统能够保持简单而非复杂化,那么他们便能够工作在最佳状态。
因此,简单性应该是设计时的关键指标,同时也要避免不必要的复杂度。
因此,简单性应该是设计时的关键指标,同时也要避免不必要的复杂度。
目的
主要目的主要是提升代码 & 架构的灵活性 / 可扩展性以及复用性。最终目标是提高软件开发速度和质量 。
让代码更容易理解:通过添加注释、命名规范、逻辑优化等手段可以让我们的代码更容易被理解。
避免代码腐化:通过重构干掉坏味道代码。
加深对代码的理解:重构代码的过程会加深你对某部分代码的理解。
发现潜在 bug:是这样的,很多潜在的 bug ,都是我们在重构的过程中发现的。
时机
提交代码之前
营地法则:保证你离开时的代码库一定比来时更健康。
当我们离开营地(项目代码)的时候,请不要留下垃圾(代码坏味道)!尽量确保营地变得更干净了!
开发一个新功能之后&之前
Code Review 之后
捡垃圾式重构
阅读理解代码的时候
注意事项
单元测试是重构的保护网。
不要为了重构而重构。
遵循方法。
单元测试
单元测试(Unit Testing)是针对程序模块(软件设计的最小单位)进行的正确性检验测试工作。
作用
为重构保驾护航、提高代码质量、减少 bug、快速定位 bug、持续集成依赖单元测试
TDD
Test-Driven Development( 测试驱动开发),这是敏捷开发的一项核心实践和技术,也是一种设计方法论。
原理
开发功能代码之前,先编写测试用例代码,然后针对测试用例编写功能代码,使其能够通过。
优点
帮你整理需求,梳理思路。
帮你设计出更合理的接口(空想的话很容易设计出屎)。
减小代码出现 bug 的概率。
提高开发效率(前提是正确且熟练使用 TDD)。
缺点
能用好 TDD 的人非常少,看似简单,实则门槛很高。
投入开发资源(时间和精力)通常会更多。
由于测试用例在未进行代码设计前写,很有可能限制开发者对代码整体设计。
可能引起开发人员不满情绪,这点很严重,毕竟不是人人都喜欢单元测试,尽管单元测试会带给我们相当多的好处。
单测框架
JUnit、Mockito、Spock、PowerMock、JMockit、TestableMock 等等。
JUnit 几乎是默认选择,但是其不支持 Mock,因此还需要选择一个 Mock 工具。Mockito 和 Spock 是最主流的两款 Mock 工具,一般都是在这两者中选择。
Mockito 和 Spock
Spock 没办法 Mock 静态方法和私有方法 ,Mockito 3.4.0 以后,支持静态方法的 Mock。
Spock 基于 Groovy,写出来的测试代码更清晰易读,比较规范(自带 given-when-then 的常用测试结构规范)。
Mockito 没有具体的结构规范,需要项目组自己约定一个或者遵守比较好的测试代码实践。
Mockito 没有具体的结构规范,需要项目组自己约定一个或者遵守比较好的测试代码实践。
Mockito 使用的人群更广泛,稳定可靠。并且,Mockito 是 SpringBoot Test 默认集成的 Mock 工具。
认证授权
认证授权
认证 (Authentication)
验证身份的凭据(例如用户名/用户 ID 和密码),通过这个凭据证明系统存在这个用户。所以 Authentication 被称为身份/用户验证。
授权 (Authorization)
授权发生在认证之后,管理用户访问系统的权限。
Cookie
定义
某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。
Cookie 存放在客户端,一般用来保存用户信息。
内容
名字,值,过期时间,路径和域,路径与域一起构成cookie的作用范围。
生命周期
表示当前cookie的生命周期为浏览器会话期间,关闭浏览器则cookie消失。
应用
Cookie 保存已经登录过的用户信息,下次再访问网站时,页面可以自动登录并自动填写一些基本信息。
Cookie 能保存用户首选项,主题和其他设置信息。
Cookie 能保存用户首选项,主题和其他设置信息。
使用 Cookie 保存 SessionId 或者 Token ,向后端发送请求的时候带上 Cookie,这样后端就能取到 Session 或者 Token 了。
这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。
这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。
Cookie 可以用来记录和分析用户行为。
Cookie 和 Session
Session 的主要作用就是通过服务端记录用户的状态,典型的场景是购物车。
Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
Session-Cookie 认证
单体环境认证过程
用户向服务器发送用户名、密码、验证码用于登陆系统。
服务器验证通过后,服务器为用户创建一个 Session,并将 Session 信息存储起来。
服务器向用户返回一个 SessionID,写入用户的 Cookie。
当用户保持登录状态时,Cookie 将与每个后续请求一起被发送出去。
服务器可以将存储在 Cookie 上的 SessionID 与存储在内存中或者数据库中的 Session 信息进行比较,
以验证用户的身份,返回给用户客户端响应信息的时候会附带用户当前的状态。
以验证用户的身份,返回给用户客户端响应信息的时候会附带用户当前的状态。
注意
依赖 Session 的关键业务一定要确保客户端开启了 Cookie。
注意 Session 的过期时间。
多节点认证方案
某个用户的所有请求都通过特性的哈希策略分配给同一个服务器处理。
每一个服务器保存的 Session 信息都是互相同步的,也就是说每一个服务器都保存了全量的 Session 信息。
单独使用一个所有服务器都能访问到的数据节点(比如缓存)来存放 Session 信息。
Spring Session 是一个用于在多个服务器之间管理会话的项目。
没有 Cookie 时 Session 还能用吗?
可以将 SessionID 放在请求的 url 里面。
攻击
CSRF(Cross Site Request Forgery)跨站请求伪造 ,说简单点,就是用你的身份去发送一些对你不友好的请求。
XSS(Cross Site Scripting)跨站脚本攻击,攻击者会用各种方式将恶意代码注入到其他用户的页面中,通过脚本盗用信息。
因与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆,因此将跨站脚本攻击缩写为 XSS。
因与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆,因此将跨站脚本攻击缩写为 XSS。
Cookie 无法防止 CSRF 攻击,而 Token 可以。
不论是 Cookie 还是 Token 都无法避免 跨站脚本攻击(Cross Site Scripting)XSS 。
JWT
定义
JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。
从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。
JWT 自身包含了身份验证所需要的所有信息,因此服务器不需要存储 Session 信息。这增加了系统的可用性和伸缩性,减轻了服务端的压力。
JWT 更符合设计 RESTful API 时的「Stateless(无状态)」原则 。
使用 JWT 认证可以有效避免 CSRF 攻击,因为 JWT 一般是存在 localStorage 中,使用 JWT 进行身份验证的过程中是不会涉及到 Cookie 的。
组成
JWT 本质上就是一组字串,通过(.)切分成三个为 Base64 编码的部分,例:xxxxx.yyyyy.zzzzz。
Header
描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。
组成
typ(Type):令牌类型,也就是 JWT。
alg(Algorithm):签名算法,比如 HS256。
Payload
用来存放实际需要传递的数据。
Payload 是 JSON 格式数据,其中包含了 Claims(声明,包含 JWT 的相关信息)。
Claims 分类
Registered Claims(注册声明):预定义的一些声明,建议使用,但不是强制性的。
Public Claims(公有声明):JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registry 中定义它们。
Private Claims(私有声明):JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。
常见注册声明
iss(issuer):JWT 签发方。
iat(issued at time):JWT 签发时间。
sub(subject):JWT 主题。
aud(audience):JWT 接收方。
exp(expiration time):JWT 的过期时间。
nbf(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。
jti(JWT ID):JWT 唯一标识。
注意
Payload 部分默认是不加密的,一定不要将隐私信息存放在 Payload 当中!!!
Signature
服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
Signature 部分是对前两部分的签名,作用是防止 JWT(主要是 payload) 被篡改。
要素
Header + Payload。
存放在服务端的密钥(一定不要泄露出去)。
签名算法。
公式
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
认证过程
用户向服务器发送用户名、密码以及验证码用于登陆系统。
如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT。
用户以后每次向后端发请求都在 Header 中带上这个 JWT 。
服务端检查 JWT 并从中获取用户相关信息。
建议
建议将 JWT 存放在 localStorage 中,放在 Cookie 中会有 CSRF 风险。
请求服务端并携带 JWT 的常见做法是将其放在 HTTP Header 的 Authorization 字段中(Authorization: Bearer Token)。
优势
相比于 Session 认证的方式。
无状态
JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。
由于 JWT 的无状态,也导致了它最大的缺点:不可控(用户 Logout 后,JWT 仍然有效)。
由于 JWT 的无状态,也导致了它最大的缺点:不可控(用户 Logout 后,JWT 仍然有效)。
有效避免了 CSRF 攻击
使用 JWT 进行身份验证不需要依赖 Cookie ,因此可以避免 CSRF 攻击。
适合移动端应用
只要 JWT 可以被客户端存储就能够使用,而且 JWT 还可以跨语言使用。
单点登录友好
单点登录 Cookie 有跨域问题,但因 JWT 被保存在客户端,不会存在这问题。
安全性
使用安全系数高的加密算法。
使用成熟的开源库,没必要造轮子。
JWT 存放在 localStorage 中而不是 Cookie 中,避免 CSRF 风险。
一定不要将隐私信息存放在 Payload 当中。
防篡改,密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。
Payload 要加入 exp (JWT 的过期时间),永久有效的 JWT 不合理。并且,JWT 的过期时间不易过长。
问题
主动失效问题
场景
退出登录、修改密码、修改权限/角色、用户的帐户被封禁/删除、用户被服务端强制注销、用户被踢下线等。
解决
将 JWT 存入内存数据库(Redis),但违背了 JWT 的无状态原则。
黑名单机制(Redis),JWT 失效就加入黑名单,但违背了 JWT 的无状态原则。
修改密钥 (Secret),为每个用户都创建一个专属密钥,如果让某个 JWT 失效,直接修改对应用户的密钥即可。
不推荐
保持令牌的有效期限短并经常轮换,但是会导致用户登录状态不会被持久记录,而且需要用户经常登录。
续签问题
类似于 Session 认证中的做法
Session:假如 Session 的有效期 30 分钟,如果 30 分钟内用户有访问,就把 Session 有效期延长 30 分钟。
JWT:如果马上快过期了,服务端就重新生成 JWT,客户端检查新旧 JWT,不一致就进行替换。
每次请求都返回新 JWT
思路很简单,但开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。
JWT 有效期设置到半夜
一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
用户登录返回两个 JWT
accessJWT 过期时间半小时,refreshJWT 过期时间1天。accessJWT 失效就校验 refreshJWT,并生成新的 accessJWT。
问题
需要客户端来配合。
用户注销的时候需要同时保证两个 JWT 都无效。
重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况。
解决:可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT。
解决:可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT。
存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。
SSO
SSO(Single Sign On)单点登录,即用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。
优点
用户角度:用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
系统管理员角度:管理员只需维护好一个统一的账号中心就可以了,方便。
新系统开发角度:新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。
设计与实现
设计
实现
登录
登录信息获取/登录状态校验
登出
跨域登录(主域名已登录)
跨域登录(主域名未登录)
跨域登出
OAuth 2.0
定义
OAuth 是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。
OAuth 2.0 是对 OAuth 1.0 的完全重新设计,OAuth 2.0 更快,更容易实现,OAuth 1.0 已经被废弃。
目的
一种授权机制,目的是为第三方应用颁发一个有时效性的令牌 Token,使得第三方应用能够通过该令牌获取相关的资源。
应用
第三方登录。
支付场景(微信支付、支付宝支付)。
开发平台(微信开放平台、阿里开放平台等等)。
权限系统设计
RBAC 模型
定义
系统权限控制最常采用的访问控制模型
RBAC 即基于角色的权限访问控制(Role-Based Access Control),一种通过角色关联权限,角色同时又关联用户的授权的方式。
一个用户可以拥有若干角色,每一个角色又可以被分配若干权限,这样就构造成“用户-角色-权限” 的授权模型。
实现
数据库表的常见设计:一共 5 张表,2 张用户建立表之间的联系。
ABAC 模型
定义
基于属性的访问控制(Attribute-Based Access Control,简称 ABAC) 是一种比 RBAC模型 更加灵活的授权模型
这个模型在云系统中使用的比较多,比如 AWS,阿里云等。
原理
通过各种属性来动态判断一个操作是否可以被允许。
一个操作是否被允许是基于对象、资源、操作和环境信息共同动态计算决定的。
对象:对象是当前请求访问资源的用户。用户的属性包括 ID,个人资源,角色,部门和组织成员身份等。
资源:资源是当前用户要访问的资产或对象,例如文件,数据,服务器,甚至 API。
操作:操作是用户试图对资源进行的操作。常见的操作包括“读取”,“写入”,“编辑”,“复制”和“删除”。
环境:环境是每个访问请求的上下文。环境属性包含访问的时间和位置,对象的设备,通信协议和加密强度等。
应用
授权某个人具体某本书的编辑权限。
当一个文档的所属部门跟用户的部门相同时,用户可以访问这个文档。
新权限模型
用户最终权限 = 用户拥有的角色带来的权限 + 用户独立配置的权限,两者取并集。
数据安全
加密算法
定义
一种用数学方法对数据进行变换的技术,目的是保护数据的安全,防止被未经授权的人读取或修改。
加密算法可以分为三大类:对称加密算法、非对称加密算法和哈希算法(也叫摘要算法)。
应用
保存在数据库中的密码需要加盐之后使用哈希算法(比如 BCrypt)进行加密。
保存在数据库中的银行卡号、身份号这类敏感数据需要使用对称加密算法(比如 AES)保存。
网络传输的敏感数据比如银行卡号、身份号需要用 HTTPS + 非对称加密算法(如 RSA)来保证传输数据的安全性。
哈希算法
哈希算法也叫散列函数或摘要算法,作用是对任意长度的数据生成一个固定长度的唯一标识,也叫哈希值、散列值或消息摘要。
作用
用来验证数据的完整性和一致性。
特点
不可逆:不能从哈希值还原出原始数据。
原始数据的任何改变都会导致哈希值的巨大变化。
分类
加密哈希算法
定义
安全性较高的哈希算法,它可以提供一定的数据完整性保护和数据防篡改能力,
能够抵御一定的攻击手段,安全性相对较高,适用于对安全性要求较高的场景。例如,SHA-256、SHA-512、SM3、Bcrypt 等等。
能够抵御一定的攻击手段,安全性相对较高,适用于对安全性要求较高的场景。例如,SHA-256、SHA-512、SM3、Bcrypt 等等。
哈希算法一般是不需要密钥的,但也存在部分特殊哈希算法需要密钥。
例如,MAC 和 SipHash 就是一种基于密钥的哈希算法,它在哈希算法的基础上增加了一个密钥,使得只有知道密钥的人才能验证数据的完整性和来源。
例如,MAC 和 SipHash 就是一种基于密钥的哈希算法,它在哈希算法的基础上增加了一个密钥,使得只有知道密钥的人才能验证数据的完整性和来源。
常见算法
MD(Message Digest,消息摘要算法):MD2、MD4、MD5 等,已经不被推荐使用。
SHA(Secure Hash Algorithm,安全哈希算法):SHA-1 系列安全性低,SHA2,SHA3 系列安全性较高。
国密算法:SM2、SM3、SM4,其中 SM2 为非对称加密算法,SM4 为对称加密算法,SM3 为哈希算法(安全性及效率和 SHA-256 相当,更适合国内)。
Bcrypt(密码哈希算法):基于 Blowfish 加密算法的密码哈希算法,专门为密码加密而设计,安全性高。
MAC(Message Authentication Code,消息认证码算法):HMAC 是一种基于哈希的 MAC,可以与任何安全的哈希算法结合使用,例如 SHA-256。
CRC(Cyclic Redundancy Check,循环冗余校验):CRC32 是一种 CRC 算法,特点是生成 32 位的校验值,通常用于数据完整性校验、文件校验等场景。
SipHash:加密哈希算法,它的设计目的是在速度和安全性之间达到一个平衡,用于防御哈希泛洪 DoS 攻击。
Rust 默认使用 SipHash 作为哈希算法,从 Redis4.0 开始,哈希算法被替换为 SipHash。
Rust 默认使用 SipHash 作为哈希算法,从 Redis4.0 开始,哈希算法被替换为 SipHash。
MurMurHash:经典快速的非加密哈希算法,目前最新的版本是 MurMurHash3,可以生成 32 位或者 128 位哈希值。
MD
定义
MD 算法有多个版本,包括 MD2、MD4、MD5 等,其中 MD5 是最常用的版本,它可以生成一个 128 位(16 字节)的哈希值。
除了这些版本,还有一些基于 MD4 或 MD5 改进的算法,如 RIPEMD、HAVAL 等。
安全性
MD5 > MD4 > MD2。
MD5 算法本身存在弱碰撞(Collision)问题,即多个不同的输入产生相同的 MD5 值。
破解
攻击者可以通过暴力破解或彩虹表攻击等方式,找到与原始数据相同的哈希值,从而破解数据。
增加破解难度
加盐。
盐(Salt)
在密码学中,是指通过在密码任意固定位置插入特定的字符串,让哈希后的结果和使用原始密码的哈希结果不相符,这种过程称之为“加盐”。
推荐
更安全的哈希算法比如 SHA-2、Bcrypt。
SHA
定义
SHA(Secure Hash Algorithm)系列算法是一组密码哈希算法,用于将任意长度的数据映射为固定长度的哈希值。
SHA 系列算法由美国国家安全局(NSA)于 1993 年设计,目前共有 SHA-1、SHA-2、SHA-3 三种版本。
版本
SHA-1
将任意长度的数据映射为 160 位的哈希值。
缺点
安全性低,容易受到碰撞攻击和长度扩展攻击。(不推荐)
SHA-2
在 SHA-1 算法的基础上改进而来的,它们采用了更复杂的运算过程和更多的轮次,使得攻击者更难以通过预计算或巧合找到碰撞。
家族
SHA-256、SHA-384、SHA-512 等。
SHA-3
美国国家标准与技术研究院(National Institute of Standards and Technology,简称 NIST)在 2007 年公开征集 SHA-3 的候选算法。
一共 64 个算法方案,最终在 2012 年 Keccak 算法胜出,成为 SHA-3 的标准算法(SHA-3 与 SHA-2 算法没有直接的关系)。
Keccak 算法
具有与 MD 和 SHA-1/2 完全不同的设计思路,即海绵结构(Sponge Construction),
使得传统攻击方法无法直接应用于 SHA-3 的攻击中(能够抵抗目前已知的所有攻击方式包括碰撞攻击、长度扩展攻击、差分攻击等)。
使得传统攻击方法无法直接应用于 SHA-3 的攻击中(能够抵抗目前已知的所有攻击方式包括碰撞攻击、长度扩展攻击、差分攻击等)。
由于 SHA-2 算法还没有出现重大的安全漏洞,而且在软件中的效率更高,所以大多数人还是倾向于使用 SHA-2 算法。
SHA-2 和 MD5
哈希值长度更长:例如 SHA-256 算法的哈希值长度为 256 位,而 MD5 算法的哈希值长度为 128 位,这就提高了攻击者暴力破解或者彩虹表攻击的难度。
更强的碰撞抗性:SHA 算法采用了更复杂的运算过程和更多的轮次,使得攻击者更难以通过预计算或巧合找到碰撞。目前 SHA-256 还没有产生碰撞。
注意
SHA-2 也不是绝对安全的,也有被暴力破解或者彩虹表攻击的风险,所以,在实际的应用中,加盐还是必不可少的。
Bcrypt
一种基于 Blowfish 加密算法的密码哈希算法,专门为密码加密而设计,安全性高。
原理
由于 Bcrypt 采用了 salt(盐) 和 cost(成本) 两种机制,它可以有效地防止彩虹表攻击和暴力破解攻击,从而保证密码的安全性。
salt 是一个随机生成的字符串,用于和密码混合,增加密码的复杂度和唯一性。
cost 是一个数值参数,用于控制 Bcrypt 算法的迭代次数,增加密码哈希的计算时间和资源消耗。
Bcrypt 算法可以根据实际情况进行调整加密的复杂度,可以设置不同的 cost 值和 salt 值,从而满足不同的安全需求,灵活性很高。
Spring Security 支持多种密码编码器,其中 BCryptPasswordEncoder 是官方推荐的一种,它使用 BCrypt 算法对用户的密码进行加密存储。
非加密哈希算法
安全性相对较低的哈希算法,易受到暴力破解、冲突攻击等攻击手段的影响,
但性能较高,适用于对安全性没有要求的业务场景。例如,CRC32、MurMurHash3 等等。
但性能较高,适用于对安全性没有要求的业务场景。例如,CRC32、MurMurHash3 等等。
对称加密
定义
对称加密算法是指加密和解密使用同一个密钥的算法,也叫共享密钥加密算法。
可以用来保护数据的安全性和保密性,常见的对称加密算法有 DES、3DES、AES 等。
DES 和 3DES
DES
定义
DES(Data Encryption Standard)使用 64 位的密钥(有效秘钥长度为 56 位,8 位奇偶校验位)和 64 位的明文进行加密。
虽然 DES 一次只能加密 64 位,但我们只需要把明文划分成 64 位一组的块,就可以实现任意长度明文的加密。
填充
如果明文长度不是 64 位的倍数,必须进行填充,常用的模式有 PKCS5Padding, PKCS7Padding, NOPADDING。
思想
将 64 位的明文分成两半,然后对每一半进行多轮的变换,最后再合并成 64 位的密文。
变换包括置换、异或、选择、移位等操作,每一轮都使用了一个子密钥,而这些子密钥都是由同一个 56 位的主密钥生成的。
总共进行了 16 轮变换,最后再进行一次逆置换,得到最终的密文。
缺点
虽经典,但也有明显的缺陷,即 56 位的密钥安全性不足,已被证实可以在短时间内破解。
3DES
3DES(Triple DES)是 DES 向 AES 过渡的加密算法,它使用 2 个或者 3 个 56 位的密钥对数据进行三次加密。
3DES 相当于是对每个数据块应用三次 DES 的对称加密算法。
为了兼容普通的 DES,3DES 并没有直接使用 加密->加密->加密 的方式,而是采用了加密->解密->加密 的方式。
当三种密钥均相同时,前两步相互抵消,相当于仅实现了一次加密,因此可实现对普通 DES 加密算法的兼容。
3DES 比 DES 更为安全,但其处理速度不高。
AES
定义
AES(Advanced Encryption Standard)算法是一种更先进的对称密钥加密算法,
它使用 128 位、192 位或 256 位的密钥对数据进行加密或解密,密钥越长,安全性越高。
它使用 128 位、192 位或 256 位的密钥对数据进行加密或解密,密钥越长,安全性越高。
AES 也是一种分组(或者叫块)密码,分组长度只能是 128 位,也就是说,每个分组为 16 个字节。
AES 加密算法有多种工作模式(mode of operation),如:ECB、CBC、OFB、CFB、CTR、XTS、OCB、GCM(目前使用最广泛的模式)。
不同的模式参数和加密流程不同,但是核心仍然是 AES 算法。
填充
对于不是 128 位倍数的明文需要进行填充,常用的填充模式有 PKCS5Padding, PKCS7Padding, NOPADDING。
AES-GCM 是流加密算法,可以对任意长度的明文进行加密,所以对应的填充模式为 NoPadding,即无需填充。
优点
AES 的速度比 3DES 快,而且更安全。
DES 算法和 AES 算法简单对比
非对称加密
定义
非对称加密算法是指加密和解密使用不同的密钥的算法,也叫公开密钥加密算法。
这两个密钥互不相同,一个称为公钥,另一个称为私钥。公钥可以公开给任何人使用,私钥则要保密。
如果用公钥加密数据,只能用对应的私钥解密(加密);如果用私钥加密数据,只能用对应的公钥解密(签名)。
可以实现数据的安全传输和身份认证。常见的非对称加密算法有 RSA、DSA、ECC 等。
RSA
RSA(Rivest–Shamir–Adleman algorithm)算法是一种基于大数分解的困难性的非对称加密算法,
原理
选择两个大素数作为私钥的一部分,然后计算出它们的乘积作为公钥的一部分(寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难)。
安全性
依赖于大数分解的难度,目前已经有 512 位和 768 位的 RSA 公钥被成功分解,因此建议使用 2048 位或以上的密钥长度。
优点
简单易用,可以用于数据加密和数字签名。
缺点
运算速度慢,不适合大量数据的加密。
DSA
DSA(Digital Signature Algorithm)算法是一种基于离散对数的困难性的非对称加密算法。
原理
选择一个素数 q 和一个 q 的倍数 p 作为私钥的一部分,然后计算出一个模 p 的原根 g 和一个模 q 的整数 y 作为公钥的一部分。
安全性
依赖于离散对数的难度,目前已经有 1024 位的 DSA 公钥被成功破解,因此建议使用 2048 位或以上的密钥长度。
优点
数字签名速度快,适合生成数字证书。
缺点
不能用于数据加密,且签名过程需要随机数。
算法签名过程
敏感词过滤
系统需要对用户输入的文本进行敏感词过滤如色情、政治、暴力相关的词汇。
算法实现
Trie 树
Trie 树也称为字典树、单词查找树,哈系树的一种变种,
通常被用于字符串匹配,用来解决在一组字符串集合中快速查找某个字符串的问题。
通常被用于字符串匹配,用来解决在一组字符串集合中快速查找某个字符串的问题。
原理
通过公共前缀来提高字符串匹配效率。
缺点
一种利用空间换时间的数据结构,占用的内存会比较大。
推荐
改进版 Trie 树,例如:双数组 Trie 树(Double-Array Trie,DAT)。
DAT
相比较于 Trie 树,DAT 的内存占用极低,可以达到 Trie 树内存的 1%左右。
DAT 在中文分词、自然语言处理、信息检索等领域有广泛的应用,是一种非常优秀的数据结构。
AC 自动机
Aho-Corasick(AC)自动机是一种建立在 Trie 树上的一种改进算法,是一种多模式匹配算法。
原理
使用 Trie 树来存放模式串的前缀,通过失败匹配指针(失配指针)来处理匹配失败的跳转。
DFA
DFA(Deterministic Finite Automata)即确定有穷自动机,一个识别器,它对每个输入的字符做识别和判断,以确定其能到达的最终状态或状态集和路径。
与之对应的是 NFA(Non-Deterministic Finite Automata,不确定有穷自动机)。
数据脱敏
定义
对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。
在数据脱敏过程中,通常会采用不同的算法和技术,以根据不同的需求和场景对数据进行处理。
规则
替换(常用):将敏感数据中的特定字符或字符序列替换为其他字符。例如,将信用卡号中的中间几位数字替换为星号(*)或其他字符。
删除:将敏感数据中的部分内容随机删除。比如,将电话号码的随机 3 位数字进行删除。
重排:将原始数据中的某些字符或字段的顺序打乱。例如,将身份证号码的随机位交错互换。
加噪:在数据中注入一些误差或者噪音,达到对数据脱敏的效果。例如,在敏感数据中添加一些随机生成的字符。
加密(常用):使用加密算法将敏感数据转换为密文。例如,将银行卡号用 MD5 或 SHA-256 等哈希函数进行散列。
工具
Hutool
一个 Java 基础工具类,对文件、流、加密解密、转码、正则、线程、XML 等 JDK 方法进行封装,组成各种 Util 工具类。
数据脱敏工具就是在 hutool.core 模块,DesensitizedUtil 工具基本覆盖了常见的敏感信息。
全局脱敏
基于 Spring Boot 的 web 项目,则可以利用 Spring Boot 自带的 jackson 自定义序列化实现。
原理
在 json 进行序列化渲染给前端时,进行脱敏。
步骤
脱敏策略的枚举。
定义一个用于脱敏的 Desensitization 注解。
@JsonSerialize
创建自定的序列化类,继承 JsonSerializer,实现 ContextualSerializer 接口,并重写两个方法。
Apache ShardingSphere
一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)相互独立的产品组成。
作用
提供标准化的数据分片、分布式事务和数据库治理功能 。
原理
Apache ShardingSphere 下面存在一个数据脱敏模块,此模块集成的常用的数据脱敏的功能。
对用户输入的 SQL 进行解析拦截,并依靠用户的脱敏配置进行 SQL 的改写,从而实现对原文字段的加密及加密字段的解密。
FastJSON
一个很常用的 Spring Web Restful 接口序列化的工具。
方式
基于注解 @JSONField 实现
需要自定义一个用于脱敏的序列化的类,然后在需要脱敏的字段上通过 @JSONField 中的 serializeUsing 指定为我们自定义的序列化类型即可。
基于序列化过滤器
需要实现 ValueFilter 接口,重写 process 方法完成自定义脱敏,然后在 JSON 转换时使用自定义的转换策略。
Mybatis-mate
MybatisPlus 也提供了数据脱敏模块 mybatis-mate。其为 MybatisPlus 企业级模块,使用之前需要配置授权码(付费),旨在更敏捷优雅处理数据。
MyBatis-Flex
类似于 MybatisPlus,MyBatis-Flex 也是一个 MyBatis 增强框架。MyBatis-Flex 同样提供了数据脱敏功能,并且是可以免费使用的。
MyBatis-Flex 提供了 @ColumnMask() 注解,以及内置的 9 种脱敏规则,开箱即用。
定时任务
定义
定时任务:在指定时间点执行特定的任务。
延时任务:一定的延迟时间后执行特定的任务。
单机定时任务
Timer
java.util.Timer 是 JDK 1.3 开始就已经支持的一种定时任务的实现方式。
原理
Timer 内部使用一个叫做 TaskQueue 的类存放定时任务,它是一个基于最小堆实现的优先级队列。
TaskQueue 会按照任务距离下一次执行时间的大小将任务排序,保证在堆顶的任务最先执行。
缺点
一个 Timer 一个线程,这就导致 Timer 的任务的执行只能串行执行,一个任务执行时间过长的话会影响其他任务(性能非常差)。
发生异常时任务直接停止(Timer 只捕获了 InterruptedException )。
无法使用 Cron 表达式指定任务执行的具体时间。
ScheduledThreadPoolExecutor 支持多线程执行定时任务并且功能更强大,是 Timer 的替代品。
ScheduledExecutorService
ScheduledExecutorService 是一个接口,有多个实现类,比较常用的是 ScheduledThreadPoolExecutor 。
ScheduledThreadPoolExecutor 本身就是一个线程池,支持任务并发执行。并且,其内部使用 DelayedWorkQueue 作为任务队列。
缺点
无法使用 Cron 表达式指定任务执行的具体时间。
DelayQueue
DelayQueue 是 JUC 包(java.util.concurrent)为我们提供的延迟队列,用于实现延时任务。
原理
它是 BlockingQueue 的一种,底层是一个基于 PriorityQueue 实现的一个无界队列,是线程安全的。
DelayQueue 和 Timer/TimerTask
DelayQueue 和 Timer/TimerTask 都可以用于实现定时任务调度,但是它们的实现方式不同。
DelayQueue 是基于优先级队列和堆排序算法实现的,可以实现多个任务按照时间先后顺序执行。
Timer/TimerTask 是基于单线程实现的,只能按照任务的执行顺序依次执行,如果某个任务执行时间过长,会影响其他任务的执行。
DelayQueue 还支持动态添加和移除任务,而 Timer/TimerTask 只能在创建时指定任务。
Spring Task
Spring 提供的 @Scheduled 注解即可定义定时任务。
优点
简单,轻量,支持 Cron 表达式。
Cron 表达式
用于定时作业(定时任务)系统定义执行时间或执行频率的表达式。
缺点
Spring 自带的定时调度只支持单机,并且提供的功能比较单一。
时间轮
定义
一个环形的队列(底层一般基于数组实现),队列中的每一个元素(时间格)都可以存放一个定时任务列表。
Kafka、Dubbo、ZooKeeper、Netty、Caffeine、Akka 中都有对时间轮的实现。
原理
时间轮中的每个时间格代表了时间轮的基本时间跨度或者说时间精度,
假如时间一秒走一个时间格的话,那么这个时间轮的最高精度就是 1 秒(也就是说 3 s 和 3.9s 会在同一个时间格中)。
假如时间一秒走一个时间格的话,那么这个时间轮的最高精度就是 1 秒(也就是说 3 s 和 3.9s 会在同一个时间格中)。
超过一圈时,则可以记录圈数/轮数,来表示更久的时间。
圈数/轮数
多层次时间轮 (类似手表)
应用
时间轮比较适合任务数量比较多的定时任务场景,它的任务写入和执行的时间复杂度都是 O(1)。
分布式定时任务
Redis
Redis 用来做延时任务。
方案
Redis 过期事件监听。
Redisson 内置的延时队列。
MQ
大部分消息队列,例如 RocketMQ、RabbitMQ,都支持定时/延时消息。
优点
可以与 Spring 集成、支持分布式、支持集群、性能不错。
缺点
功能性较差、不灵活、需要保障消息可靠性。
分布式任务调度框架
需要一些高级特性比如支持任务在分布式场景下的分片和高可用的话,我们就需要用到分布式任务调度框架了。
角色
任务:首先肯定是要执行的任务,这个任务就是具体的业务逻辑比如定时发送文章。
调度器:其次是调度中心,调度中心主要负责任务管理,会分配任务给执行器。
执行器:最后就是执行器,执行器接收调度器分派的任务并执行。
Quartz
Quartz 是 Java 定时任务领域的参考标准,其他的任务调度框架基本都是基于 Quartz 开发的,如当当网的 elastic-job。
优点
使用 Quartz 可以很方便地与 Spring 集成,并且支持动态添加任务和集群。
缺点
Quartz 使用起来也比较麻烦,API 繁琐。
Quartz 并没有内置 UI 管理控制台。
Quartz 虽然支持分布式任务,但在数据库层面,通过数据库的锁机制做的,有非常多的弊端比如系统侵入性严重、节点负载不均衡。
Elastic-Job
当当网开源的一个面向互联网生态和海量任务的分布式调度解决方案,由两个相互独立的子项目 ElasticJob-Lite 和 ElasticJob-Cloud 组成。
原理
Elastic-Job 没有调度中心这一概念,而是使用 ZooKeeper 作为注册中心,注册中心负责协调分配任务到不同的节点上。
Elastic-Job 中的定时调度都是由执行器自行触发,这种设计也被称为去中心化设计(调度和处理都是执行器单独完成)。
ElasticJob-Lite 和 ElasticJob-Cloud
ElasticJob-Lite 的架构设计
优点
可以与 Spring 集成、支持分布式、支持集群、性能不错、支持任务可视化管理。
缺点
依赖了额外的中间件比如 Zookeeper(复杂度增加,可靠性降低、维护成本变高)
XXL-JOB
XXL-JOB 于 2015 年开源,是一款优秀的轻量级分布式任务调度框架,支持任务可视化管理、弹性扩容缩容、任务失败重试和告警、任务分片等功能,
特性
架构设计
组成
调度中心
主要负责任务管理、执行器管理以及日志管理。进行任务调度时,是通过自研 RPC 来实现的。
执行器
主要是接收调度信号并处理。
使用
只需要重写 IJobHandler 自定义任务执行逻辑就可以了,非常易用!
优点
开箱即用(学习成本比较低)、与 Spring 集成、支持分布式、支持集群、支持任务可视化管理。
缺点
不支持动态添加任务(如果一定想要动态创建任务也是支持的)。
PowerJob
非常值得关注的一个分布式任务调度框架,分布式任务调度领域的新星。目前,已经有很多公司接入比如 OPPO、京东、中通、思科。
消息推送
定义
指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。
一般又分为 Web 端消息推送和移动端消息推送。
轮询
轮询(polling) 应该是实现消息推送方案中最简单的一种,分为短轮询和长轮询。
短轮询
指定的时间间隔,由浏览器向服务器发出 HTTP 请求,服务器实时返回未读消息数据给客户端,浏览器再做渲染显示。
优缺点
虽简单,但由于推送数据并不会频繁变更,无论是否有新的消息产生,客户端都会进行请求,会对服务端造成很大压力,浪费带宽和服务器资源。
长轮询
对上边短轮询的一种改进版本,在尽可能减少对服务器资源浪费的同时,保证消息的相对实时性。
原理
如果服务端的数据没有发生变更,会一直 hold 住请求,直到服务端的数据发生变化,或者等待一定时间超时才会返回。
返回后,客户端又会立即再次发起下一次长轮询。
返回后,客户端又会立即再次发起下一次长轮询。
应用
在中间件中应用的很广泛,比如 Nacos 和 Apollo 配置中心,消息队列 Kafka、RocketMQ 中都有用到长轮询。
iframe 流
定义
在页面中插入一个隐藏的<iframe>标签,通过在src中请求消息数量 API 接口,由此在服务端和客户端之间创建一条长连接,服务端持续向iframe传输数据。
传输的数据通常是 HTML、或是内嵌的 JavaScript 脚本,来达到实时更新页面的效果。
缺点
iframe 流的服务器开销很大,而且 IE、Chrome 等浏览器一直会处于 loading 状态,图标会不停旋转,非常不友好,不推荐。
SSE
定义
SSE(Server-Sent Events)服务器发送事件,一种服务器端到客户端(浏览器)的单向消息推送。
原理
SSE 基于 HTTP 协议。
在服务器和客户端之间打开一个单向通道,
服务端响应的不再是一次性的数据包而是 text/event-stream 类型的数据流信息,在有数据变更时从服务器流式传输到客户端。
服务端响应的不再是一次性的数据包而是 text/event-stream 类型的数据流信息,在有数据变更时从服务器流式传输到客户端。
SSE 与 WebSocket
作用相似,都可以建立服务端与浏览器之间的通信,实现服务端向客户端推送消息。
SSE 是基于 HTTP 协议的,它们不需要特殊的协议或服务器实现即可工作;WebSocket 需单独服务器来处理协议。
SSE 单向通信,只能由服务端向客户端单向通信;WebSocket 全双工通信,即通信的双方可以同时发送和接受信息。
SSE 实现简单开发成本低,无需引入其他组件;WebSocket 传输数据需做二次解析,开发门槛高一些。
SSE 默认支持断线重连;WebSocket 则需要自己实现。
SSE 只能传送文本消息,二进制数据需要经过编码后传送;WebSocket 默认支持传送二进制数据。
兼容性
应用
ChatGPT
Websocket
定义
一种在 TCP 连接上进行全双工通信的协议,建立客户端和服务器之间的通信渠道。
浏览器和服务器仅需一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
步骤
客户端向服务器发送一个 HTTP 请求,请求头中包含 Upgrade: websocket 和 Sec-WebSocket-Key 等字段,表示要求升级协议为 WebSocket。
服务器收到这个请求后,会进行升级协议的操作,如果支持 WebSocket,它将回复一个 HTTP 101 状态码,
响应头中包含 ,Connection: Upgrade和 Sec-WebSocket-Accept: xxx 等字段、表示成功升级到 WebSocket 协议。
响应头中包含 ,Connection: Upgrade和 Sec-WebSocket-Accept: xxx 等字段、表示成功升级到 WebSocket 协议。
客户端和服务器之间建立了一个 WebSocket 连接,可以进行双向的数据传输。
数据以帧(frames)的形式进行传送,而不是传统的 HTTP 请求和响应。
WebSocket 的每条消息可能会被切分成多个数据帧(最小单位)。
发送端会将消息切割成多个帧发送给接收端,接收端接收消息帧,并将关联的帧重新组装成完整的消息。
数据以帧(frames)的形式进行传送,而不是传统的 HTTP 请求和响应。
WebSocket 的每条消息可能会被切分成多个数据帧(最小单位)。
发送端会将消息切割成多个帧发送给接收端,接收端接收消息帧,并将关联的帧重新组装成完整的消息。
客户端或服务器可以主动发送一个关闭帧,表示要断开连接。另一方收到后,也会回复一个关闭帧,然后双方关闭 TCP 连接。
注意
建立 WebSocket 连接之后,通过心跳机制来保持 WebSocket 连接的稳定性和活跃性。
MQTT
定义
MQTT(Message Queue Telemetry Transport)是一种基于发布/订阅(publish/subscribe)模式的轻量级通讯协议,
通过订阅相应的主题来获取消息,是物联网(Internet of Thing)中的一个标准传输协议。
通过订阅相应的主题来获取消息,是物联网(Internet of Thing)中的一个标准传输协议。
该协议将消息的发布者(publisher)与订阅者(subscriber)进行分离,
因此可以在不可靠的网络环境中,为远程连接的设备提供可靠的消息服务,使用方式与传统的 MQ 有点类似。
因此可以在不可靠的网络环境中,为远程连接的设备提供可靠的消息服务,使用方式与传统的 MQ 有点类似。
TCP 协议位于传输层,MQTT 协议位于应用层,MQTT 协议构建于 TCP/IP 协议上,也就是说只要支持 TCP/IP 协议栈的地方,都可以使用 MQTT 协议。
优势
首先 HTTP 协议它是一种同步协议,客户端请求后需要等待服务器的响应。
而在物联网(IOT)环境中,设备会很受制于环境的影响,比如带宽低、网络延迟高、网络通信不稳定等,显然异步消息协议更为适合 IOT 应用程序。
而在物联网(IOT)环境中,设备会很受制于环境的影响,比如带宽低、网络延迟高、网络通信不稳定等,显然异步消息协议更为适合 IOT 应用程序。
HTTP 是单向的,如果要获取消息客户端必须发起连接,
而在物联网(IOT)应用程序中,设备或传感器往往都是客户端,这意味着它们无法被动地接收来自网络的命令。
而在物联网(IOT)应用程序中,设备或传感器往往都是客户端,这意味着它们无法被动地接收来自网络的命令。
通常需要将一条命令或者消息,发送到网络上的所有设备上。HTTP 要实现这样的功能不但很困难,而且成本极高。
0 条评论
下一页