跟随黑马面Java-10,企业场景篇
Joker2Yue第十篇:企业场景篇
“求其上,得其中;求其中,得其下,求其下,必败”
开篇
设计模式
工厂方法模式
需求:设计一个咖啡店点餐系统。
-
咖啡类(Coffee):定义基本的咖啡属性和行为。
- 美式咖啡(AmericanCoffee):继承自咖啡类,表示美式咖啡的特性。
- 拿铁咖啡(LatteCoffee):继承自咖啡类,表示拿铁咖啡的特性。
-
咖啡店类(CoffeeStore):具有点咖啡的功能,可以根据客户需求制作不同种类的咖啡。
具体类的设计如下:
如果我们是这样的话,那么就违背了软件开发的开闭原则:扩展开放,对修改关闭
简单工厂模式
-
简单工厂包含如下角色:
-
抽象产品:定义了产品的规范,描述了产品的主要特性和功能
-
具体产品:实现或者继承抽象产品的子类
-
具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
-
-
咖啡改简单工厂
这样虽然解决了
CoffeeStore
和Coffee
的耦合,但是又产生了新的耦合,CoffeeStore
和SimpleCoffeeFactory
,以及SimpleCoffeeFactory
和Coffee
的耦合。后续我们要添加新品种的咖啡,就势必要修改SimpleCoffeeFactory
。
工厂方法模式
-
工厂方法模式的主要角色包括:
-
抽象工厂(AbstractFactory):提供创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
-
具体工厂(ConcreteFactory):实现抽象工厂中的抽象方法,完成具体产品的创建。
-
抽象产品(Product):定义产品的规范,描述产品的主要特性和功能。
-
具体产品(ConcreteProduct):实现了抽象产品定义的接口,由具体工厂来创建,与具体工厂之间一一对应。
-
-
咖啡改工厂方法
现在我们就将
CoffeeStore
和Coffee
的耦合解开了。但在添加新产品的时候需要添加新产品的工厂和具体的类。
-
优缺点
-
优点:
用户只需知道具体工厂的名称即可获取所需的产品,无需了解产品的创建细节。
当系统需要增加新产品时,只需添加相应的具体产品类和对应的具体工厂类,而无需修改原工厂类,符合开闭原则。
-
缺点:
每增加一个产品都需要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
-
抽象工厂设计模式
工厂方法模式只考虑生产同等级的产品,抽象工厂可以处理多等级产品的生产。
-
产品族是指一个品牌下的所有产品,例如华为的电脑和手机属于华为的产品族。产品等级是指多个品牌下相同种类的产品,例如华为和小米都有手机和电脑,手机和电脑构成了一个产品等级。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只能生产一个等级的产品,而抽象工厂模式可以生产多个等级的产品。一个超级工厂创建其他工厂,该超级工厂称为其他工厂的工厂。
-
咖啡改抽象工厂
-
优缺点
优点:当一个产品族中的多个对象需要一起工作时,抽象工厂模式可以保证客户端始终只使用同一个产品族中的对象。
缺点:当产品族需要增加一个新的产品时,所有的工厂类都需要进行修改。
策略模式
-
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。它通过对算法进行封装,将使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
-
主要角色:
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
-
类图
-
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class TravelContext {
private TravelStrategy travelStrategy;
public TravelContext(TravelStrategy travelStrategy) {
this.travelStrategy = travelStrategy;
}
public void selectTravel() {
this.travelStrategy.travel();
}
public static void main(String[] args) {
TravelContext travelContext = new TravelContext(new Aircraft()); // 在这里可以选择交通方式
travelContext.selectTravel();
}
} -
优缺点
-
优点
- 策略类之间可以自由切换
- 易于扩展
- 避免使用多重条件选择语句(if else),充分体现面向对象设计思想
-
缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
- 策略模式将造成产生很多策略类
-
工厂模式+策略模式(登录案例)
我们提供多种登录方式,需要一个工厂来根据参数获取不同的策略对象。现在的 Service 不再使用 if-else,而是调用工厂。Service 需要提供参数,比如 account
,然后工厂根据参数获取相应的策略对象。例如,如果获取到的是 WeChat,就返回给 Service 执行。
然而,如果新增一种登录方式,虽然不用修改 Service 代码,但仍需修改工厂。因此,仍需进一步优化。最终的目标是无论新增多少登录方式,都无需修改任何代码。
我们将让 Spring 容器管理所有策略对象,工厂将执行两个操作:1)读取配置,准备对象;2)匹配 Service 传递的参数并返回。
如果现在需要新增 QQ 登录,只需新增一个 QQ 对象,并在配置文件中添加即可。
-
举一反三
订单支付策略(支付宝、微信、银行卡)
解析不同类型的 Excel(xls 格式、xlsx 格式)
打折促销(满300元9折、满500元8折、满1000元7折…)
物流运费阶梯计算(5kg 以下、5-10kg、10-20kg、20kg 以上)
-
总结:只要代码中有冗长的if-else或switch分支判断都可以采用策略模式优化
责任链模式
责任链模式:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
比如Spring中的拦截器、过滤器等都是责任链模式的典型应用。
-
角色
- 抽象处理者(Handler)角色:定义了处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(ConcreteHandler)角色:实现了抽象处理者的处理方法,判断是否能处理本次请求,如果能处理则进行处理,否则将请求传递给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
-
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* 抽象处理者
*/
public abstract class Handler {
protected Handler handler;
public void setNext(Handler handler) {
this.handler = handler;
}
/**
* 处理过程
* 需要子类进行实现
*/
public abstract void process(OrderInfo order);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 设置责任链
*/
public static void main(String[] args) {
// 检验订单
Handler orderValidation = new OrderValidation();
// 补充订单信息
Handler orderFill = new OrderFill();
// 订单算价
Handler orderAmountCalculate = new OrderAmountCalculate();
// 订单落库
Handler orderCreate = new OrderCreate();
// 设置责任链路
orderValidation.setNext(orderFill);
orderFill.setNext(orderAmountCalculate);
orderAmountCalculate.setNext(orderCreate);
// 开始执行
orderValidation.process(new OrderInfo());
} -
优缺点:
优点:
-
降低了对象之间的耦合度
-
增强了系统的可扩展性和灵活性
-
简化了对象之间的连接,实现了责任分担
-
提高了给对象指派职责的灵活性
缺点:
-
对于比较长的职责链,请求的处理可能涉及多个处理对象,影响系统性能
-
职责链的建立需要合理设置,由客户端保证,增加了客户端的复杂性,可能会因为错误设置导致系统出错,例如循环调用的问题。
-
-
举一反三
技术场景
单点登录是如何实现的
单点登录的英文名叫做:Single Sign On(简称SSO),只需要登录一次,就可以访问所有信任的应用系统。
-
引入
-
单体项目
在单体项目中,当用户登录以后,就可以将用户的信息存入Session中,在需要用户信息的时候,直接从Session中获取就可以。单个tomcat服务,session是可以共享的。
-
微服务
随着项目规模更替,单体项目转变为微服务项目,之前是一个tomcat部署,现在是多个。假如还是下单的操作,session是不能共享的。我们不能让用户在下单的时候,再登录一次吧?
-
SSO解决方案:JWT(常见)、Oauth2、CAS
-
-
JWT解决单点登录
浏览器首先发起下单操作,网关会检查其token是否有效,若无效则返回401状态码,导致浏览器显示登录页面。用户在页面上输入用户名密码后,再发起登录请求。请求被网关路由到登录服务,用于验证用户信息。验证成功后,登录服务会返回一个token(JWT生成)。浏览器会将此token写入cookie中。这样,用户再次下单时会携带此token。网关再次检查此token的有效性,如果有效,则正常路由到订单服务去处理订单。
权限认证是如何验证的
后台管理系统更注重权限控制,常见的实现方式之一是RBAC(Role-Based Access Control,基于角色的访问控制)模型。它包含了三个基础的部分,用户,角色,权限。
-
具体实现
5张表(用户表、角色表、权限表、用户角色中间表、角色权限中间表)
7张表(用户表、角色表、权限表、菜单表、用户角色中间表、角色权限中间表、权限菜单中间表)
(RBAC权限模型,其实就是3张基础表,2张映射表。或者4张基础表,3张映射表。)
-
5表实现
张三具有什么权限呢?
流程:张三登录系统->查询张三拥有的角色列表->再根据角色查询拥有的权限。
一般还需要通过框架来实现用户的鉴权。常见的是Apache shiro、Spring security (推荐)
上传数据的安全性怎么控制
使用非对称加密(或对称加密),给前端一个公钥让他把数据加密后传到后台,后台负责解密后处理数据
-
对称加密
文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥
-
优点:加密速度快,效率高。
-
缺点:相对不太安全(不适合保存敏感信息)。
-
-
非对称加密
两个密钥:公开密钥(publickey)和私有密钥,公有密钥加密,私有密钥解密
-
优点:相较于对称加密,安全性更高。
-
缺点:加密和解密速度慢,建议少量数据加密。
-
你负责项自的时候遇到了哪些比较棘手的问题?怎么解决的
你们项目中日志是怎么采集的
-
关于日志采集
-
为什么要采集日志
日志是定位系统问题的重要手段,可以根据自志信息快速定位系统中的问题。
-
采集日志的方式有哪些
ELK:即Elasticsearch、Logstash和Kibana三个软件的首字母
常规采集:按天保存到一个日志文件
-
-
ELK
常见日志的命令
-
查看日志的命令:
-
实时监控日志的变化:
- 实时监控某一个日志文件的变化:
tail -f xx.log
- 实时监控日志最后100行日志:
tail -n 100 -f xx.log
- 实时监控某一个日志文件的变化:
-
按照行号查询:
- 查询日志尾部最后100行日志:
tail -n 100 xx.log
- 查询日志头部开始100行日志:
head -n 100 xx.log
- 查询某一个日志行号区间:
cat -n xx.log | tail -n +100 | head -n 100
(查询100行至200行的日志)
- 查询日志尾部最后100行日志:
-
按照关键字找日志的信息:
- 查询日志文件中包含"debug"的日志行:
cat -n xx.log | grep "debug"
- 查询日志文件中包含"debug"的日志行:
-
按照日期查询:
- 使用
sed
命令按照日期范围查询:sed -n '/2023-05-18 14:22:31.070/,/2023-05-18 14:27:14.158/p' xx.log
- 使用
-
日志太多的处理方式:
- 分页查询日志信息:
cat -n xx.log | grep "debug" | more
- 筛选过滤以后,输出到一个文件:
cat -n xx.log | grep "debug" > debug.txt
- 分页查询日志信息:
-
生产环境的问题怎么排查
-
已经上线的 bug 排查的思路:
-
分析日志:首先分析日志。通常业务中会有日志记录,可以查看系统日志或日志文件,然后定位问题。
-
远程调试:远程调试是一种常见的排查方式,但通常不允许在正式环境(生产环境)进行。远程调试通常在公司的测试环境中进行,以便方便调试代码。
-
-
远程debug
前提条件:远程代码和本地代码保持一致
-
远程调试代码需要配置启动参数,在将项目打包放到服务器后,启动项目的命令如下:
1
java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 project-1.0-SNAPSHOT.jar
-
在 IntelliJ IDEA 中设置远程调试,可以在 “Edit Configurations” 中进行配置。
-
idea中启动远程debug
-
访问远程服务器,在本地代码中打断点即可调试远程
-
怎么快速定位系统的瓶颈
-
测试
- 在系统上线之前进行压力测试(性能测试),以评估系统的承受能力。
- 上线后使用监控工具和链路追踪工具来监控系统。
- 使用线上诊断工具Arthas(阿尔萨斯)对项目进行监控和排查。 Arthas能够帮助实时诊断线上问题,提升系统的稳定性和可靠性。
-
压测(性能测试)
- 目的:评估系统当前的性能状况,定位系统性能瓶颈或潜在性能瓶颈。
- 指标:响应时间、QPS(每秒请求数)、并发数、吞吐量、CPU利用率、内存使用率、磁盘IO、错误率。
- 压测工具:LoadRunner、Apache JMeter等。
- 后端工程师:根据压测结果进行解决或调优,如接口慢、代码报错、并发达不到要求等。
-
监控工具、链路追踪工具
- 监控工具:使用Prometheus+Grafana等工具进行监控。
- 链路追踪工具:如SkyWalking、Zipkin等。
-
线上诊断工具Arthas(阿尔萨斯)
- 官网:https://arthas.aliyun.com/
- 核心功能:提供线上诊断功能,能够帮助实时诊断线上问题,提升系统的稳定性和可靠性。
-
火焰图阅读法:
-
理解火焰图的结构
- 纵轴表示调用栈的深度,从下往上依次是被调用的函数。最底层是程序入口,最顶层是当前正在执行的函数。
- 横轴表示采样数或执行时间,方块的宽度越大,表示该函数被采样到的次数越多,执行时间越长。
-
识别性能热点
-
关注顶层占据横向空间较大的函数,这些函数可能存在性能瓶颈。
-
沿着调用栈路径向下查看,找出自身执行时间较长的函数调用。
-
-