Fork me on GitHub

西北游玩指北 置顶

仅记录生活,照片见qq相册

西北,不仅是个地理概念,更是中国人的西部情节,有机会,就去西北游玩吧。

1. 旅行tips

1. 交通

成都-兰州西 ¥200

兰州西-中川机场 ¥17*2

9:38 的飞机,11:40 到敦煌机场,¥740

机场到莫高窟,参观3小时 ¥200

鸣沙山门票¥220 骆驼¥240 敦煌打车¥90 吃饭¥70

从敦煌20:43到嘉峪关23:57, ¥100*2 住宿¥90

嘉峪关坐出租车参观¥110,到10点

从嘉峪关南10:04到张掖西11:34 ¥65.5*2,张掖西到丹霞景区,打车¥6 大巴¥60 七彩丹霞景区¥148

张掖西18:31到西宁20:25 ¥91.5*2

西宁租车¥223,住宿¥107

第二天6点开车去青海湖环游,加油¥300

西宁的饭¥80*2

西宁回兰州¥58 回成都¥240.5

2. 小件

雨伞、充电宝、采购鸡腿和面包

3. 知识

参考出租车费:沙洲市场-莫高窟22;莫高窟-鸣沙山35;鸣沙山-沙洲市场15;沙洲夜市-机场30

2. 旅途

青海湖

去过两次,记录下

西宁站–>青海湖–>茶卡盐湖–>青海湖(看是否有时间环湖)–>塔尔寺–>西宁站

ditu

路上可能稍微有点堵车,到青海湖的黑马河乡地图时间3h左右,实际可能4h,路上可以问藏民哪个地方可以直接进去(一般10元),黑马河乡到茶卡盐湖地图时间1h30min,到茶卡盐湖坐小火车到天空之境拍照

青海湖还有其他几个地方拍照比较好:达秀木拉布宰、青海湖二郎剑景区对面的停车场,往青海湖南山行驶10分钟就可以找到理想的观景点

有兴趣的话,回西宁的路上再加上塔尔寺

高速到茶卡盐湖,平均150码左右,国道到黑河马乡,在菜根香小炒吃的土火锅,非常赞,第二天看完日出从国道开回西宁,平均100码左右,公路景观非常棒,跟游戏的画面似的,梦幻般的感觉。

费用明细:

  • 高速: 大水桥65 倒淌河21 日月山4 倒淌河33 另外的15
  • 加油:200
  • 晚饭:120
  • 茶卡盐湖的门票:300(各150,包括坐小火车到天空之境),停车费10块
  • 火车票:兰州到西宁往返 116*2
  • 旅馆:75
  • 旅途中草原上骑马:100(各50)用现金,那边(路途中间)没网…
  • 租车:40(剩余油费)+279(租车)+2000(违章押金,待退还)

青海湖日出

羊群

茶卡盐湖

茶卡盐湖

航天城

敦煌

莫高窟、鸣沙山

鸣沙山

嘉峪关

古长城

长城

张掖

七彩丹霞

丹霞

宁夏

甘肃到宁夏的高速开到了180码,经历了真正的沙尘天气,还是挺有感觉的。

宁夏

兰州

兰州美食:兰州古称金城,特色的兰州牛肉面、甜醅子、三炮台,其他的如大盘鸡、烧烤都差不多。

省博里面看到很多有意思的文物,可能地处边陲,年代尚早,记录着汉民族少年阶段的探索,笔法稚嫩,但就是比后面充满匠气的文物更有精神气。

甘肃省博

麦积烟雨

石窟开凿的痕迹还很明显,不像中原的登峰造极,更让我们看到佛法西来的历史脚印

麦积山

人生警示录

曾国藩的六勤

  • 身勤 不要假大空,而是亲身实践,实践出真知
  • 眼勤 善于观察 冰鉴 里面有具体实操
  • 手勤 坚持每天日记复盘
  • 口勤 善于沟通和协调,言辞恳切充实,参考黄奇帆,感觉很厉害
  • 心勤 勤于思考,谋定而后动,类似写代码要先写好设计,再去写具体代码
  • 脚勤 实地走访获取一手信息,实事求是,多走动

感觉好久没有这么真切的感悟了,自己一直在浪费自己的事件,希望能够在AI时代真正稳住主业,并用AI好好搞自己的代码,提升能力

设计模式的落地

为什么需要掌握设计模式

类比于我们的成语、行话,助力我们的大脑快速高效的沟通和研读优秀的代码

1. 六大原则

介绍

1. 单一职责原则

Single Responsibility Principle, SRP,一个类只负责一个功能领域的相应职责。也就是我们常说的“高内聚,低耦合”

2. 开闭原则

Open-Closed Principle,OCP:对扩展开放,对修改关闭

也就是尽量在不修改原有代码的情况下进行扩展

3. 里式替换原则

Liskov Substitution Principle,LSP:所有引用父类的地方必须能透明的使用其子类的对象。

在程序中尽量使用基类类型来对对象进行定义,在运行时再确定子类类型,用子类对象来替换父类对象。

算是实现开闭原则的重要方式之一,通俗的说:子类可以扩展父类的功能,但不能改变父类原有的功能

4. 依赖倒置原则

Dependency Inversion Principle,DIP:抽象不应该依赖于细节,细节应该依赖于抽象,也就是面向接口编程,而不是针对实现编程

开闭原则是目标,里式替换是基础,依赖倒置是手段。

感觉和spring的DI有点联系,后续再思考。

5. 接口隔离原则

Interface Segregation Principle,ISP:使用多个专门的接口,而不适用单一的总接口,即客户端不应该依赖那些它不需要的接口

每个接口应该承担相对独立的角色,提供定制服务,当然接口也不能太小,灵活性会变差。控制好接口的粒度。

6. 迪米特法则

Law of Demeter,LoD, 也叫最少知识原则,LeastKnowledge Principle,LKP:一个软件实体应当尽可能少的与其他实体发生相互作用

也就是解耦合,降低系统的耦合度。

分类

大致按照设计模式的应用目标分类,分为创建型、结构型和行为型

  • 创建型模式,是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式(Factory, Abstract Factory)、单例模式(Singleton)、构建者模式(Builder)、原型模式(Prototype)
  • 结构型模式,是对软件设计结构的总结,专注于类、对象继承、组合方式的实践经验。常见的有桥接模式(Bridge)、适配器模式(Adapter)、装饰器模式(Decorator)、代理模式(Proxy)、组合模式(Composite)、门面模式(Facade)、享元模式(Flyweight)等。
  • 行为型模式,是从类或者对象之间交互、职责划分等角度总结的模式,常见有策略模式(Strategy)、解释器模式(Interpreter)、命令模式(Command)、观察者模式(Observer)、迭代器模式(Iterator)、模板方法模式(Template Method)、访问者模式(Visitor)

单例模式

之前总结有,https://blog.csdn.net/wjl31802/article/details/91360815

单例模式最常见,必须掌握。spring默认就是单例模式

适配器模式

封装有缺陷的接口设计

可以看作一种补偿模式,也就是设计初期未考虑到一些接口,比如登录没考虑到第三方登录,这时候可以用。

具体类图如下
Pasted image 20240810114413

实现的效果如下
Pasted image 20240831104325

适配器(Adapter)角色,既能够支持已有功能(用户名/密码登录)​,也能够适配扩展功能(第三方账号授权登录)​,适配的扩展功能还能够复用已有的方法(register方法和login方法)

具体gitee的实现详见

设计模式-适配器模式-通过gitee登录应用系统

统一多个类的接口设计

某个功能的实现依赖多个外部系统(或者说类),敏感词过滤为例,系统可能依赖多个敏感词库,提高过滤的召回率。但是他们的接口实现都不太一样,通过适配器把接口适配为统一的接口定义,保证复用性。

Pasted image 20240831120842

替换外部依赖A为外部依赖B

Pasted image 20240831182711

兼容老版本的接口

版本升级后,部分接口被弃用,标注deprecated,但是仍然有部分项目在用,如何丝滑的过渡?

jdk给我们的demo,jdk1.0的容器遍历的类Enumeration,在JDK1.2的时候改为Iterator类,那如何兼容呢?采用适配器模式,暂时保留 Enumeration 类,并将其实现替换为直接调用 Itertor

适配不同格式的数据

不同格式的数据之间的适配,比如,把从不同征信系统拉取的不同格式的征信数据,统一为相同的格式,以方 便存储和使用。再比如,Java 中的 Arrays.asList() 也可以看作一种数据适配器,将数组类 型的数据转化为集合容器类型。

不同日志框架的适配

2023年爆出的log4j的漏洞让全国人民都知道了这个日志框架。
java的日志框架比较混乱,或者说多元,因为最开始没有和数据库连接一样制定相关的接口规范。
梳理下大致的发展:

  1. 最开始只有System.out.print这种,没有日志级别啥的
  2. 2001年log4j诞生,定义了Logger Appender Level等概念,但是同步锁导致高并发下的性能有问题
  3. sun公司jdk1.4也搞了jul包java.util.logging 但是性能和功能都一般
  4. 为了适配这两个,搞了个接口,JCLL(Jakarta Commons Logging),在ClassLoader里面找log4j,没有就用JUL,但是性能太差
  5. log4j的作者写了个slf4j接口,为了追求性能,又搞了logback,完全兼容
  6. 那log4j由于性能问题,Apache宣布2015年不再维护,就又搞了log4j2,但是它跟log4j不兼容

那问题来了,如果不同的项目,又用到logback,又用到log4j,然后呢想用slf4j,怎么整?
就考虑到适配器模式,把不同的框架接口二次封装,适配为统一的slf4j接口定义。

桥接模式

桥接模式也叫作桥梁模式(Bridge Pattern),该模式旨在将抽象和实现解耦

Pasted image 20240810164618

还是以三方登录为例子,核心逻辑的实现需要在“右路Implementor的结构体系中

要实现什么功能?

  1. 实现三方登录
  2. 维持现有的注册登录

implementor的实现

1
2
3
4
5
6
public interface RegisterLoginFuncInterface {  
public String login(String account,String password);
public String register(UserInfo userInfo);
public boolean checkUserExist(String userName);
public String login3rd(HttpServletRequest request);
}

然后就是具体的实现,这里面就有问题了,比如RegisterLoginByDefault是否要实现login3rd?RegisterLoginByGitee是否要实现login? 通过抽象层解决

abstract的实现

需要提供给client这些方法:login抽象方法、register抽象方法、checkUserExists抽象方法和第三方账号登录的login3rd抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class AbstractRegisterLoginComponent {  
// 桥梁
protected RegisterLoginFuncInterface funcInterface;
public AbstractRegisterLoginComponent(RegisterLoginFuncInterface funcInterface) {
validate(funcInterface);
this.funcInterface = funcInterface;
}

protected final void validate(RegisterLoginFuncInterface funcInterface) {
if (!(funcInterface instanceof RegisterLoginFuncInterface)) {
throw new UnsupportedOperationException("unknown register/login function type!");
}
}

public abstract String login(String username, String password);
public abstract String register(UserInfo userInfo);
public abstract boolean checkUserExists(String userName);
public abstract String login3rd(HttpServletRequest request);

}

用“抽象和实现”两种类结构的设计换来了代码的高扩展性,换来了核心实现对Client端的“最少知识”原则,换来了耦合度的降低,就好比我们用空间换来了执行速度。

继续左路突破,RegisterLoginComponent子类

搭建桥梁

通过在AbstractRegisterLoginComponent抽象类中关联RegisterLoginFuncInterface接口,并以“构造函数”的形式,初始化RegisterLoginFuncInterface接口属性,完成抽象与实现的桥梁搭建。

Pasted image 20240907184925

调用

Controller层→Service层→桥接模式的左路的抽象AbstractRegisterLoginComponent入口

service层调用具体的登录实现,不能直接new,这样的话,每个进行login登录的用户线程,都会new两个对象,一个是左路的抽象子类RegisterLoginComponent作为调用入口;一个是右路具体的子类,如RegisterLoginByDefault。对于用户量庞大且活跃度较高的应用,这种代码很可能会引起频繁的垃圾收集操作。

引入工厂类RegisterLoginComponentFactory进行RegisterLoginComponent对象的生成和缓存。

利用@PostConstruct注解,在RegisterLoginByDefault对象和RegisterLoginByGitee对象注入到SpringBoot的容器后,初始化funcMap

补充

桥接模式的核心其实就是桥梁,本质也是组合模式,只是扩展性更强。

监控告警可以采用这种,Notification是抽象,MsgSender是实现,具体有短信告警、电话告警等

组合模式

解释

将对象组合成树形结构以表示“部分—整体”的层次结构

Pasted image 20240907194633

  • Component抽象角色。所有树形结构的叶子节点和非叶子节点都需要继承该抽象角色
  • Composite树枝构件角色 非叶子节点
  • Leaf叶子构件角色

但是有些过时,现实场景,不能保证叶子节点永远不会没有子节点。因此再继续优化。
Pasted image 20240818173156

实现

设计要点:

  • addProductIteam方法和delProductIteam方法,都不强制子类进行实现,因此没有使用abstract修饰这两个方法,子类可以根据需求自主选择实现哪些方法。
  • 参数都用AbstractProductIteam本身,遵循李氏替换原则

对应controller service和repository

其中,generateProductTree方法是将数据库的商品类目信息转化成组合模式树形结构的核心代码

访问者模式

概念

旨在操作某对象结构中各个(各层级)元素的模式,在不改变元素整体结构的前提下,定义作用于这些元素的新操作。

商品类目的树形结构来说,我们可以利用访问者模式,对树形结构的任意节点进行操作(添加、删除)​。

Pasted image 20240907225018

除Client调用端以外,访问者模式包含5个角色,其中两个角色属于组合模式,我仅仅需要搞定其他3个角色即可

  • Visitor抽象访问者。接口或抽象类均可,定义访问者能够访问的数据类型
  • ConcreteVistor具体访问者 定义支持商品类目添加的具体访问者和支持商品类目删除的具体访问者
  • ObjectStructure 数据提供者 Client先通过ObjectStructure获取树形商品类目数据,再调用Visitor对第1步获取的树形商品类目数据进行访问操作
  • Component被访问者抽象角色
  • Composite被访问者具体角色

Visitor

1
2
3
public interface ItemVisitor<T> {  
T visitor(AbstractProductItem productItem);
}

ConcreteVistor

ObjectStructure数据提供者

其实就是RedisCommonProcessor,从缓存获取数据

Pasted image 20240907230308

controller service和repository

无论是商品类目添加还是商品类目删除,代码的实现逻辑并未涉及补偿机制以及极端场景

状态模式

概念

Pasted image 20240818190853

  • State抽象状态角色:该角色主要进行状态的定义和方法的定义。
  • ConcreteState具体状态类:不同的状态需要创建不同的状态类(订单有四个状态,就需要创建四个具体的状态类)​,并且实现抽象状态类定义的方法。
  • Context上下文角色(也称环境角色)​:封装状态的转化逻辑,是状态的转化过程的容器,暴露给客户端使用。可以类比Spring框架的ApplicationContext角色。

缺点:

  1. 使得service层形同虚设,逻辑全在context中
  2. 并发的问题,不同的订单不能使用同一个context,context有状态。但是无状态的话,违背UML类图的结构

解决:

采用spring状态机

概念

状态机是状态模式的一种应用,相当于上下文角色的一个升级版,在工作流状态转化、订单状态转化等各种系统中有大量使用,封装状态的变化规则。

  • state(状态)​:如订单的不同状态
  • transition(转移)​:一个状态接收一个输入,执行了某些动作到达了另外一个状态的过程就是一个transition(转移)​。例如,订单状态在ORDER_WAIT_PAY的情况下,接收到了一个支付动作,那么订单状态就会从ORDER_WAIT_PAY状态transition(转移)到ORDER_WAIT_SEND状态。
  • transition condition (转移条件)​:也叫作Event(事件)​,在某一状态下,只有达到了transition condition(转移条件)​,才会按照状态机的转移流程转移到下一状态,并执行相应的动作。例如,订单状态在ORDER_WAIT_PAY的情况下,接收到了一个支付动作,并且支付成功了,此处的支付成功就是transition condition(转移条件)​。
  • action (动作)​:在状态机的运转过程中会有很多种动作,此处的动作是一个统称,如进入动作—在进入状态时、退出动作—在退出状态时、输入动作—依赖于当前状态和输入条件进行、转移动作—在进行特定转移时进行

基于Spring状态机进行实战需要创建的类:

  • 订单对象类
  • Context上下文环境类 不需要
  • 代表订单状态的枚举类(enum),四个订单状态
  • 代表订单操作的枚举类(enum)
  • 转化过程配置到Spring状态机中,需要创建基于@Configuration注解的配置类

Pasted image 20240908072241

具体实现

创建订单状态枚举类

1
2
3
4
5
6
public enum OrderState {  
ORDER_WAIT_PAY, //待支付
ORDER_WAIT_SEND, // 待发货
ORDER_WAIT_RECEIVE, // 待收货
ORDER_FINISH; // 完成订单
}

订单操作枚举类

1
2
3
4
5
public enum OrderStateChangeAction {  
PAY_ORDER, // 支付操作
SEND_ORDER, // 发货操作
RECEIVE_ORDER; //收货操作
}

订单对象类

1
2
3
4
5
6
7
8
9
10
11
@Data  
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Order {
private String orderId;
private String productId;
private OrderState orderState;
private Float price;
}

状态机配置类OrderStateMachineConfig

  • 设置初始状态
  • 配置转换流程
  • 状态机自身的存储和读取 spring-statemachine-redis

观察者模式

核心就两个:观察者observer和被观察者subject,再加上各自的子类

缺点:很难优雅的把观察者添加到observerList中

解决:对observersList属性的初始化过程中,我们需要引入一个用fianl static修饰的Vector数据结构,并通过@PostConstruct注解初始化Vector中的元素

基于状态机的观察者模式

作用:

  1. 监听器 监听对象
  2. 监听到状态变化,所采取的行动逻辑

通过@OnTransition注解,标注source和target属性,代表了该方法专门用于监听订单状态从ORDER_WAIT_PAY到ORDER_WAIT_SEND的转化

PayToSend方法的参数Message<OrderStateChangeAction>message,此处使用org.springframework.messaging.Message类,对OrderStateChangeAction订单操作枚举类进行了封装,从字面意义来说,这个参数代表了一条订单操作的消息。我们可以根据这个消息,获取订单的信息,并进行后续处理

controller、service

雷点

所有的第三方平台的支付操作,都是两段式的:第一段向第三方支付平台提交订单支付请求;第二段第三方支付平台回调我们平台的接口。只有在第二段的时候,我们才能确认订单支付是否成功,才能确认是否需要将订单状态转为待发货状态。

其他注意:

  • 订单存储到Redis中,是以orderId作为key的。创建完订单后,可以打开Redis客户端,通过get具体的orderId命令查看当前存储的订单信息。当订单签收成功后,Redis中的订单信息就会被删除
  • 状态机存储到Redis中,是以具体的orderId+STATE为key的

命令模式

概念

将请求封装到一个命令(Command)对象中,实现了请求调用者和具体实现者之间的解耦

Pasted image 20240908115357

  • 抽象命令(Command)角色 一般定义为接口,用于定义执行命令的接口
  • 具体命令(ConcreteCommand)角色 与命令接收者进行关联,调用命令接收者的方法
  • 接收者(Receiver)角色 真正执行命令的对象。订单转化流程的相关逻辑,都在此处进行实现。接收者可以有多个,主要根据业务需求而定
  • 调用者(Invoker)角色 接收客户端正确的命令,并触发命令的执行
  • 客户端(Client)角色 创建Invoker和命令,并通过invoker触发命令

实现

接收者OrderCommandReceiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component  
public class OrderCommandReceiver {
// 接收命令后执行
public void action(Order order) {
switch (order.getOrderState()) {
case ORDER_WAIT_PAY:
System.out.println("创建订单:order = " + order);
System.out.println("存入db");
return;
case ORDER_WAIT_SEND:
System.out.println("支付订单:order = " + order);
System.out.println("存入db");
System.out.println("通过 MQ 通知财务");
System.out.println("通过 MQ 通知物流");
return;
case ORDER_WAIT_RECEIVE:
System.out.println("订单发货:order = " + order);
System.out.println("存入db");
return;
case ORDER_FINISH:
System.out.println("接收订单:order = " + order);
System.out.println("存入db");
return;
default:
throw new UnsupportedOperationException("Order state error");
}
}
}

抽象命令OrderCommandInterface

1
2
3
public interface OrderCommandInterface {  
void execute(Order order);
}

具体命令OrderCommand

1
2
3
4
5
6
7
8
9
@Component  
public class OrderCommand implements OrderCommandInterface{
@Autowired
private OrderCommandReceiver receiver;
@Override
public void execute(Order order) {
this.receiver.action(order);
}
}

命令调用者Invoker

1
2
3
4
5
public class OrderCommandInvoker {  
public void invoke(OrderCommandInterface command, Order order) {
command.execute(order);
}
}

调用

OrderStateListener类的调用

详见之前的观察者模式的OrderStateListener

1
2
OrderCommandInvoker invoker = new OrderCommandInvoker();  
invoker.invoke(orderCommand,order);

service的调用,详见 [[设计模式重新整理#controller、service]]

策略模式

概念

指对象有某个行为,但是在不同的场景中,该行为有不同的实现逻辑,即不同的策略,实现方式不同

Pasted image 20240908121422
哈哈,看着跟状态模式一样,

  • Strategy抽象策略角色:该角色主要进行策略方法的定义
  • ConcreteStrategy具体策略类:不同的策略需要创建不同的策略类(在多种类支付的实战中,支付宝支付策略和微信支付策略就是具体的策略类)​,并且实现抽象策略类定义的方法。
  • Context上下文角色(也称环境角色)​:关联抽象策略类,并调用策略类的方法

实现

第三方支付,如支付宝

抽象策略类PayStrategyInterface

1
2
3
4
public interface PayStrategyInterface {  
// 返回值类型string 访问第三方支付,平台返回一个url地址,让用户进行支付
String pay(Order order);
}

具体策略AlipayStrategy

context

Service层调用的是门面模式的封装层,门面模式调用策略工厂及策略模式的相关类

策略模式和策略工厂的配合使用,就不需要将context修改成无状态类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class AbstractPayContext {  
public abstract String execute(Order order);
}

public class PayContext extends AbstractPayContext{
private PayStrategyInterface payStrategy;

public PayContext(PayStrategyInterface payStrategy) {
this.payStrategy = payStrategy;
}

@Override
public String execute(Order order) {
return this.payStrategy.pay(order);
}
}

门面模式

解释

旨在封装。可以封装一个需求模块,如多种类支付模块,也可以封装一个复杂的系统,从设计模式的角度看Spring Cloud Gateway,Gateway就是一个门面,Spring Cloud Gateway提供了所有请求的访问入口,为后续无数的微服务模块提供了门面。

  • Facade门面角色:该角色暴露给调用者进行调用。作为门面角色,它知道子系统的所有功能,门面角色会将客户端发来的请求转发到子系统中,转发之前,可以进行一定的类型转换和参数封装等辅助逻辑
  • subsystem子系统角色:子系统角色可以简单到一个类,也可以复杂到多个系统。当然,针对第三方支付需求来说,子系统角色就是策略模式的Context环境类——PayContext

实现

facade可以直接new创建Context类和具体的策略类,但是每次支付都创建,对内存产生很大的压力,导致频繁的minorGC

工厂模式

概念

  • Product:抽象产品角色
  • ConcreteProduct:具体产品角色。此处我们具体需要创建的对象是PayContext类
  • Creator:抽象工厂角色
  • ConcreteCreator:具体工厂角色

Pasted image 20240908130056

实现

享元模式

解释

旨在解决重复对象的内存浪费问题,为重复的对象创建缓冲池。享元模式最经典的就是池技术,如String常量池、数据库连接池等都是享元模式的经典应用。我们对享元模式的使用,就是通过Map作为本地缓存,减少PayContext对象的重复创建。

Pasted image 20240908202443

  • FlyWeight角色:抽象享元角色。定义对象的抽象方法和属性,对应我们的AbstractPayContext类
  • ConcreteFlyweight角色:具体享元角色。对应我们的PayContext类
  • FlyWeightFactory角色:享元工厂。提供对象的创建、缓存及获取,对应我们的PayContexFactory类
  • Client角色:3个箭头指向,意思是Client既能调用FlyWeightFactory享元工厂角色,又能直接调用具体的实现类,对应我们的PayFacaded类
  • UnsharedFlyweight角色:不可共享的角色,示例中没有

Pasted image 20240908202653

Spring IOC就是一个大型享元模式的应用,里边就有UnsharedFlyweight角色

Pasted image 20240908202721

  • FlyweightFacotry Spring的Bean Factory,就是一个享元工厂,BeanFactory就是采用的ConcurrentHashMap进行单例对象的缓存,减少对象的创建
  • Flyweight 使用@Controller、@Service、@Component等注解标注我们的类,Spring会根据我们标注的注解进行检索,找到标注注解的具体的类,将对象托管到Spring的容器中。诸如@Controller、@Service、@Component等注解,都是依托于java.lang.annotation包实现的。因此,可以将java.lang.annotation.*视为抽象享元角色
  • ConcreteFlyweight 通过@Autowired注解将对象注入到我们的类中,全局只有一个对象,完全共享,不会对内存产生压力
  • UnsharedFlyweight 对于一些对象,我们无法将其设置为单例对象,每次使用该对象,我们需要使用new关键字进行创建,或者我们可以使用@Scope注解,将对象标记为多例prototype对象,在每次使用对象时,都会重新创建一个全新的对象
  • Client调用者 可以通过Spring的上下文对象ApplicationContext,调用BeanFactory的getBean方法获取单例对象;可以直接通过new关键字创建具体对象,即使对象是单例的,我们依然可以通过new关键字创建对象;可以直接通过new关键字创建多例对象

责任链

解释

  • Handler抽象责任角色:抽象责任角色负责定义抽象方法,并且需要在该角色中定义nextHandler。开篇我们就提到过,责任链模式就类似于链表,创建链表节点时,我们会定义next节点属性。同理,此处我们定义nextHandler属性,将多个责任类定义到一个责任处理链条中
  • ConcreteHandler具体责任角色:具体的责任类。根据本章的实战需求,我们需要定义三个具体责任类:按用户购物种类筛选的责任类、按用户所在城市筛选的责任类和按用户性别筛选的责任类
  • Client责任链的装配者、使用者:Client在进行责任链的调用前,需要对责任链条进行装配,说直白些,就是将多个具体责任类,通过nextHandler属性进行连接,因此Client角色的作用分为两个:装配责任链条、调用责任链条的头节点
    Pasted image 20240825201408

实现

需求描述

业务投放场景

不同的用户可能会收到不同的投放内容,我们需要根据用户不同的属性进行投放业务的筛选,只展示符合当前用户的投放信息

返回信息之前,增加点判断条件对信息进行筛选

业务那边要求实时调整筛选条件

实时调整代表业务部门可以根据需求随意组合筛选条件。举个例子,我们的代码中定义了“按用户购物种类的筛选逻辑”​“按用户所在城市的筛选逻辑”和“按用户性别的筛选逻辑”​,如果业务部门想要将某条投放信息展示给所有女性用户,业务部门只需要使用“按用户性别的筛选逻辑”​;那如果业务部门想要将某条信息投放给所有的居住在北京的女性用户,业务部门就需要使用“按用户性别的筛选逻辑”和“按用户所在城市的筛选逻辑”​。

一些广告商想要作投放推广,投放全国女性用户和投放北京女性用户的价格肯定是不一样的啊。咱们目前只是入门级别的,把城市作为最小的投放单元。微信的广告投放更狠,都是按照方圆公里数进行投放的,投放多少公里以内的符合某些条件的用户。

Apollo是携程框架部门研发的开源配置管理中心,能够集中化管理应用的不同环境、不同集群的配置,配置修改后能够实时推送到应用端,而且Apollo还有UI操作界面,完全满足投放的业务需求。业务部门可以在Apollo的UI界面修改筛选条件,然后单击发布,新的刷选条件就会实时地推送到咱们的项目

Apollo配置中心+责任链模式。筛选条件目前只需要支持三个:​“按用户购物种类的筛选逻辑”​“按用户所在城市的筛选逻辑”和“按用户性别的筛选逻辑

实现

需求:

  1. 在Apollo中配置的duty.chain的值是“city,sex,product”​,我们如何将它们转化成CityHandler→SexHandler→ProductHandler这样的责任对象链条
  2. 如何确保,只有在第一次初始化责任链条或者是duty.chain的配置发生改变时,才去重新进行责任链条的组装

采用了枚举类+反射责任类创建+synchronized同步代码块+链表哑结点的简易算法进行实现,并且使用了两个全局变量控制组装时机,只有在第一次进行责任链条初始化和duty.chain配置更新时,才会触发责任链条的重新组装

Pasted image 20240825202221
责任类

组装和调用

装饰器模式

解释

Pasted image 20240826172210

  • Component抽象构件:Component是一个接口或者抽象类,定义我们核心的原始对象。本章实战,我们需要对OrderService的pay方法进行装饰,因此此处的Componet抽象构件,就是OrderService的抽象父类
  • ConcreteComponent具体构件:你要装饰的就是它,就是我们的OrderService类
  • Decorator抽象装饰角色:定义装饰器的属性和新的方法。此处我们的积分更新/红包发放方法的定义,就是在该类中进行的
  • ConcreteDecorator具体装饰角色:新的装饰功能的具体实现类。具体实现积分更新/红包发放逻辑,服务降级逻辑也在此处

实现

需求

平台积分更新和红包发放,在支付之后,并且需要做服务降级

  • 0代表正常服务,每次商品支付成功后,直接进行更新操作
  • 1代表延迟服务,遇上秒杀或双11活动,需要进行延迟更新,比如说凌晨0点开始秒杀,可以将积分更新或红包发放在30分钟后进行处理
  • 2代表暂停服务,平台可以随时将积分更新和红包发放业务暂停

实现

apollo设置延时时间+rabbitmqTTL机制和死信队列实现延迟服务+装饰器隔离支付和积分红包的服务

Pasted image 20240909173849

建造者模式

概念

  • Product产品类:要建造的对象,本章对应我们的电子发票类
  • Builder抽象建造者:负责产品的组建,定义产品组建的抽象方法
  • ConcreteBuilder具体建造者:实现抽象类定义的所有方法,并且返回一个组建好的对象。根据本章需求,返回电子发票对象
  • Director导演类:负责指导Builder进行对象创建,也可以称之为指挥者

Pasted image 20240826204457

实现

需求分析

开发票,分为个人和企业
个人电子发票只需要填写抬头,企业电子发票需要填写税号、抬头还有银行卡等信息

使用建造者模式可以。

扩展性呢?

指挥者角色,这个角色主要负责信息的拼装,一般不负责信息的校验和获取。比如,将银行卡信息校验放到指挥者角色中不太合适,产品信息的获取放到这里也不合适,为指挥者角色做一个代理类,代理类中会有这些辅助的校验和辅助信息。后续新增其他逻辑的时候,直接在代理类中作修改就行,非常方便,扩展性很不错

此外,支持clone

两个意义:
一是对于发票的不可变信息,我们可以直接克隆,减少new关键字的使用,因为发票信息中,有一部分信息是固定不变的;
二是考虑未知的备份操作,一张电子发票开具后,可能不同的部门有不同的使用方式,支持Clone的话,可以直接在生成好的电子发票对象上进行Clone操作,生成两个相同的电子发票对象,分别进行不同的使用

实现

product产品类

builder建造者

director导演

director搞了个AbstractDirector抽象类,为了程序的扩展性,银行卡信息的校验放在代理类,此外后续可能有其他的校验

上面也是代理模式

  • Subject抽象主题角色:被代理角色的抽象角色。拿本章需求来说,AbstractDirector类便是抽象主题角色
  • RealSubject具体主题角色:被代理角色。也就是我们的Director类
  • Proxy代理角色:在该角色中,我们要代理Director类的方法,并且增加前置的校验处理

原型模式

概念

Pasted image 20240826211305

  • Prototype原型抽象角色:原型模式的抽象角色,就是Java自带的java.lang.Cloneable接口,我们无须自主创建该角色
  • ConcretePrototype原型具体角色:实现Cloneable接口的对象。依照本章的实战需求,我们需要让PersonalTicket和CompanyTicket实现Cloneable接口。

实现

上个发票实现cloneable就是原型模式

这里涉及到 引用拷贝、深拷贝和浅拷贝的区别

引用拷贝:
创建了teacherWang对象,然后创建teacherSun对象等于teacherWang对象,teacherWang和teacherSun共用一个对象的内存地址,整个过程中,堆内存中只有一个Teacher对象

浅拷贝:
实现cloneable接口,只能拷贝最外层的对象。拿Teacher对象来说,通过浅拷贝,我们会在堆内存中创建新的Teacher对象,然而Teacher对象中的Student对象却不会被重新创建。

teacherSun和teacherWang指向不同的Teacher对象,但两个Teacher对象却指向同一个Student对象

深拷贝:
可以拷贝深层的student对象
如何实现?

  • 让所有层次的对象,都实现Cloneable接口,并实现Object的Clone方法
  • 通过Serializable序列化接口,实现深拷贝 大部分都是这种

中介者模式

概念

中介者模式可以通过中介者对象来封装一系列的对象交互,将对象间复杂的关系网状结构变成结构简单的以中介者为核心的星形结构,简化对象间的关系。同时,中介者模式能够将各个对象之间的关系解耦,每个对象不再与它关联的对象直接发生相互作用,而是通过中介者对象与关联的对象进行通信。

产品进销存系统中进行应用,包括我们的微信、QQ聊天,也都是采用了中介者模式的设计思想

Pasted image 20240827103053

  • Mediator抽象中介者:定义不同同事之间的信息交互方法
  • ConcreteMediator具体中介者:实现抽象中介者的方法,它需要知道所有的具体同事类,同时需要从具体的同事类那里接收信息,并且向其他具体的同事类发送信息。
  • Colleague抽象同事类:以本章为例,该抽象角色是购买者和帮忙支付者的父类。
  • ConcreteColleague具体同事类:每个具体同事类都只需要知道自己的行为即可,但是他们都需要认识中介者。以本章为例,购买者和帮忙支付者就是具体同事类,两个类都需要了解中介者类

跟代理模式的区别:
只负责中转,不处理具体事务

跟观察者模式的区别:
介入具体业务,对象的交互,eventbus属于观察者模式

缺点:
容易膨胀,变成大而复杂的上帝类

实现

需求分析

朋友代付

对于公司来说,咱们只关心商品的订单 订单谁支付、通过什么方式支付都不要紧,只要支付的时候,是当前的商品订单就行。
朋友代付功能,无非就是把商品订单信息转发到待支付的朋友那里,然后请朋友帮忙支付就行了

订单消息和支付结果中转的场景

  • 订单消息中转 把我要购买的商品订单转发给我的朋友
  • 支付结果中转 朋友付完钱,不得告诉你一声

实现

Mediator抽象中介者AbstractMediator

1
2
3
4
5
6
7
8
9
public abstract class AbstractMediator {  
/**
* 消息交互
* @param orderId 用户的订单ID
* @param targetCustomer 目标用户,比如说张三请李四帮忙支付,那么李四便是目标用户
* @param customer 抽象同事类 中介者需要根据该参数确定调用者的角色——购买者或帮忙支付者
* @param payResult 只有支付成功后此参数才不为null
*/ public abstract void messageTransfer(String orderId, String targetCustomer, AbstractCustomer customer, String payResult);
}

Colleague抽象同事类AbstractCustomer

  • 关联中介者,因为购买者和支付者都需要直接与中介者打交道,因此需要关联中介者类
  • 定义与中介者进行消息交互的方法
  • 有一些核心属性,如需要支付的订单号及当前客户信息等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class AbstractCustomer {  
//关联中介者
public AbstractMediator mediator;
//订单ID
public String orderId;
//当前用户信息
public String customerName;

public AbstractCustomer(AbstractMediator mediator, String orderId, String customerName) {
this.mediator = mediator;
this.orderId = orderId;
this.customerName = customerName;
}

public String getCustomerName() {
return this.customerName;
}

// 和中介者的信息交互方法
public abstract void messageTransfer(String orderId, String targetCustomer, String payResult);
}

ConcreteColleague具体同事类Buyer和Payer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Buyer extends AbstractCustomer{  

public Buyer(AbstractMediator mediator, String orderId, String customerName) {
super(mediator, orderId, customerName);
}

@Override
public void messageTransfer(String orderId, String targetCustomer, String payResult) {
super.mediator.messageTransfer(orderId,targetCustomer,this,payResult);
}
}

public class Payer extends AbstractCustomer{

public Payer(AbstractMediator mediator, String orderId, String customerName) {
super(mediator, orderId, customerName);
}

@Override
public void messageTransfer(String orderId, String targetCustomer, String payResult) {
super.mediator.messageTransfer(orderId,targetCustomer,this,payResult);
}
}

ConcreteMediator具体中介者Mediator

  • 具体中介者需要知道所有的同事类,因此该类需要关联同事类
  • 具体中介者需要实现抽象中介者的方法,作为不同同事类之间的信息交互桥梁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component  
public class Mediator extends AbstractMediator{
public static Map<String, Map<String, AbstractCustomer>> customerInstances = new ConcurrentHashMap<>();

@Override
public void messageTransfer(String orderId, String targetCustomer, AbstractCustomer customer, String payResult) {
if (customer instanceof Buyer) {
AbstractCustomer buyer = customerInstances.get(orderId).get("buyer");
System.out.println("Friend pays on behalf: "+buyer.getCustomerName() + " transfer orderId "+ orderId+" to customer "+targetCustomer+" to pay");
} else if (customer instanceof Payer) {
AbstractCustomer payer = customerInstances.get(orderId).get("payer");
System.out.println("payment completed on behalf "+payer.getCustomerName()+" completed orderId "+orderId+" pay, notify "+targetCustomer+", pay result: "+payResult);
customerInstances.remove(orderId);
}
}

public void addInstance(String orderId, HashMap<String, AbstractCustomer> map) {
customerInstances.put(orderId, map);
}
}

Pasted image 20240910140421

保证全局唯一中介者

通过Map<String, Map<String, AbstractCustomer>>数据结构存储购买者和实际支付者的信息。其中,外层Map的key为OrderId,内层Map的key为Buyer和Payer,value为AbstractCustomer对象

代支付完成后,从Map中删除此orderId的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service  
public class OrderService implements OrderServiceInterface{
@Autowired
private Mediator mediator;
public void friendPay(String sourceCustomer, String orderId, String targetCustomer, String payResult, String role) {
//创建中介者
Buyer buyer = new Buyer( mediator, orderId,sourceCustomer);
Payer payer = new Payer( mediator, orderId,sourceCustomer);
HashMap<String, AbstractCustomer> map = new HashMap<>();
map.put("buyer", buyer);
map.put("payer",payer);
mediator.addInstance(orderId,map);
if (role.equals("B")) {
buyer.messageTransfer(orderId,targetCustomer,payResult);
} else if (role.equals("P")) {
payer.messageTransfer(orderId,targetCustomer,payResult);
}
}
}

模板方法模式

概念

定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

白话版:模板方法模式会定义好做一件事情的步骤,这个步骤是固定的,一部分步骤已经实现好了,你无须关心;另外一部分步骤没有做任何实现,需要子类进行实现

对于已经定义好的步骤,我们称之为基础方法,需要子类进行实现的方法称之为模板方法
Pasted image 20240827144906

  • AbstractClass抽象模板对象:抽象模板对象主要负责基本方法的实现以及抽象模板方法的定义
  • ConcreteClass具体模板对象:对抽象模板方法进行个性实现,不会改变整体的执行结构

Spring源码的核心refresh方法里面的postProcessBeanFactory方法和onRefresh方法就是两个模板方法,与我们距离最近的模板方法模式的使用案例,就是AQS源码中的使用。

实现

需求

审计日志

记录四种订单日志即可。每种订单日志有相似的部分,都有用户操作时间、操作类型、订单ID和用户ID

  • 订单创建日志:无其他特殊信息,包含刚才描述的相似内容即可
  • 订单支付日志:除相似内容外,还需要记录支付类型和实际支付金额
  • 订单发货日志:除相似内容外,还需要记录快递公司和快递单编号
  • 订单签收日志:无其他特殊信息,包含刚才所说的相似内容即可

这些日志放到哪里,数据处理部门给我们提供了一个Queue,我们直接将信息发送到Queue里就行。

实现

Pasted image 20240827150149

模型OrderAuditLog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data  
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderAuditLog {
//当前用户信息
private String account;
// 用户操作
private String action;
// 用户操作具体时间
private Date date;
private String orderId;
// 其他额外信息
private Object details;
}

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class AbstractAuditLogProcessor {  
//创建我们的auditLog 基础方法 不允许子类重写
private final OrderAuditLog basicAuditLog(String account, String action, String orderId) {
OrderAuditLog auditLog = new OrderAuditLog();
auditLog.setAccount(account);
auditLog.setAction(action);
auditLog.setOrderId(orderId);
auditLog.setDate(new Date());
return auditLog;
}

//定义抽象模板方法,设置订单审计日志的额外信息,供子类进行实现
protected abstract OrderAuditLog buildDetails(OrderAuditLog auditLog);

//定义订单审计日志的创建步骤 不允许子类重写
public final OrderAuditLog createAuditLog(String account, String action, String orderId) {
//设置审计日志的基本信息
OrderAuditLog auditLog = basicAuditLog(account, action, orderId);
// 设置额外信息
return buildDetails(auditLog);
}
}

具体实现如CreateOrderLog PayOrderLog 等过程

1
2
3
4
5
6
7
@Component  
public class CreateOrderLog extends AbstractAuditLogProcessor{
@Override
protected OrderAuditLog buildDetails(OrderAuditLog auditLog) {
return auditLog;
}
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service  
public class OrderService implements OrderServiceInterface{
@Autowired
private CreateOrderLog createOrderLog;

public Order createOrder(String productId) {
//订单生成的逻辑
String orderId = "OID" + productId;
Order order = Order.builder()
.orderId(orderId)
.productId(productId)
.orderState(OrderState.ORDER_WAIT_PAY)
.build();
redisCommonProcessor.set(orderId,order,900);
OrderCommandInvoker invoker = new OrderCommandInvoker();
invoker.invoke(orderCommand,order);
// 暂时 testAccount 后续可以从用户中心获取account
createOrderLog.createAuditLog("testAccount", "create", orderId);
return order;
}
}

2024新年规划

背景

过年回家,感觉真的是自己长大了,我们父母都是60左右了,已经步入老年,小孩也开始上小学,有了真正的压力,如果自己再不顶上去,恐怕很惨很惨。

很痛心自己没有早点用上自己的人脉,转行之后又是虚度3年光阴,希望能抓紧时间,成为业务专家,不再沦为底层。

340724fa0727f0bf50410582a4afd2e

规划

其实今年开年对自己来说还是挺好的,自己所处的行业是软件行业,也会接触到底层的很多东西,需要快速反馈,快速学习,保持这两天的状态,不能分散精力,先把通话模块完全搞定!

工作

2-3月份,搞定通话模块大部分内容

4-5月份,搞定算法刷题

6-7月份,重拾面试技巧

8月份,跟公司提离职,或者去上海分公司入职

最关键的半年,一定要顶住,加油,成败在此一举!

房子

3月份之前把房子挂出去

孩子

2-4月份,搞定洪恩识字的所有常用字,每天晚上抽查

上半年,跟上斑马的课程进度,英语能学会常用的单词

债务

把房子卖了之后就差不多了,重新开始新生活,每个月也要挤出来1w

周计划

每周要有至少两篇整理的文章

中国人口2023

人口乌云

大家都对中国人口下降隐隐约约有个预期,但没想到下降的这么惨烈

前段时间,工程院院士、北大医学部主任公开表示,2023年的出生人口恐怕只有700-800万,700多万是什么概念,可能需要对比大家才有感觉,我们的巴铁,预测今年是647万,印度预测是2306万。我们也就是印度的1/3,和巴铁相当,而我们的断崖式下降才刚开始…

如何破局?

可以放宽绿卡,引入巴铁人口,好歹是盟邦,也是黄种人,比广州的黑叔叔强多了,能短时间内抵御人口灰犀牛的冲击,至于长期,也不是我们所能想到的了,毕竟我们的同化能力是无与伦比的,只是代价比较大而已,五胡乱华、五代十国殷鉴不远,新疆可能更绿了,其他都还好,先苟住再雄起嘛,肉食者不想发钱补贴民众,就只能接受次优解了。

参考

linux命令拾遗

jenkins流水线构建时,有tar命令

1
sh 'tar --transform="flags=r;s|${NAME}${DIST_PATH}|src|" -czf /tmp/$UPLOAD_FILE ${NAME}${DIST_PATH}'

--transformtar命令的一个选项,用于指定文件名的转换规则。

  • flags=r:这部分指定了转换规则的标志。在这种情况下,r表示替换(replace)
  • s|${NAME}${DIST_PATH}|src|:这部分是实际的转换规则,使用了sed命令的替换语法。它指定了源文件名和目标文件名的替换关系。${NAME}${DIST_PATH}是源文件名,src是目标文件名。具体来说,它将tar归档中的文件名${NAME}${DIST_PATH}替换为src

jenkins基础

基础概念

持续集成CI

概念

continuous integration,持续集成的重点是构建编译和测试,通过自动化的构建(主要是构建编译、自动化测试)来验证,从而尽早发现集成错误。

持续集成的工作流程

  • 初始化CI流程 基本的CI流程的配置,如脚本、定时任务
  • 拉取最新代码 从gitlab仓库拉取最新代码到构建服务器磁盘
  • 构建 通过配置的脚本触发执行构建,如java构建一般基于maven或gradle
  • 执行测试 一般包括单元测试和集成测试,Java的单元测试默认Junit
  • 结果处理 一般都要通知给对应人员,如邮件、钉钉、短信

持续交付CD

continuous delivery,将产品尽快的发布上线的过程。持续交付是在持续集成的基础上的扩展,也就是说除了自动化编译、自动化测试,为尽快上线还要自动化发布

持续部署

continuous deployment,在持续交付的基础上,将编译、测试、打包部署到生产环境的过程实现自动化。当然,一般非生产环境保证自动化,生产环境还是手动。

DevOps

概念

开发和运维的组合,打破开发和运维的壁垒,通过工具链,将开发、测试、运维的工作串联起来,将全部流程自动化,减少人力重复投入,降低人为风险。

工具链

  • 编码 代码开发和审查、源码管理工具、代码合并 gitlab
  • 构建 持续集成工具 Jenkins maven
  • 测试 持续测试工具 selenium Jmeter
  • 发布 变更管理、发布审批、发布自动化
  • 容器平台 docker k8s
  • 配置 基础设置配置和管理 Ansible
  • 监控 应用性能监控、终端用户体验 logstash nagios kibana zabbix

jenkins

文档 https://www.jenkins.io/zh/doc/

背景

前身hudson,Oracle收购sun之后hudson归Oracle所有,贡献者基于hudson改为jenkins。目前hudson已停止开发,Jenkins成为使用最多的CICD工具。持续集成是Jenkins的核心功能。

应用场景

  • 集成svn/git客户端实现源码checkout
  • 集成maven/ant/gradle/npm等构建工具实现源码编译打包和单元测试
  • 集成ansible实现自动化部署发布
  • 集成jmeter/k8s…
  • 可自定义插件或脚本通过Jenkins传参运行
  • 非常灵活,日常运维工作都可自动化

版本

  • Jenkins 2.x 主流,支持pipeline

  • Jenkins X 基于k8s的持续集成

搭建

主从集群,将构建任务分发到多个从节点执行,支撑多个项目的大量构建任务

部署方式:

  • 服务器直接运行war
  • 服务器yum下载Jenkins安装包
  • docker容器运行

环境说明

  • centos7

  • jdk1.8

  • maven3.x

  • git 1.8.3

  • jenkins 最新版本

  • master主机 centos30

  • slave主机 centos31

master安装

jdk
1
2
3
4
5
6
7
8
9
10
# 上传jdk包
# 配置Java环境变量
vi /etc/profile
export JAVA_HOME=/opt/jdk1.8.0_301
export JRE_HOME=/opt/jdk1.8.0_301/jre
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
# 使用source命令,使其生效
source /etc/profile
# 验证安装成功
java -version
git安装
1
2
3
yum install -y git
# 验证是否安装成功
git --version
sshpass安装
1
2
# 远程连接工具,备份恢复使用
yum install sshpass -y
wget安装
1
yum install wget -y
axel安装

多线程下载工具

1
2
wget http://download-ib01.fedoraproject.org/pub/epel/7/x86_64/Packages/a/axel-2.4-9.el7.x86_64.rpm
rpm -ivh axel-2.4-9.el7.x86_64.rpm
TODO 创建Jenkins用户
1
2
useradd -d /home/jenkins -m -s /bin/bash jenkins
# 密码
maven安装
1
2
3
4
5
6
7
8
9
# 上传压缩包,解压到/opt
# 配置环境变量
vi /etc/profile
export MAVEN_HOME=/opt/apache-maven-3.8.5
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin:$MAVEN_HOME/bin
# source
source /etc/profile
# 验证
mvn -v

配置镜像加速

1
2
3
4
5
6
7
8
9
10
maven下的conf/settings.xml找到<mirrors>和</mirrors>标签,添加
vi /opt/apache-maven-3.8.5/conf/settings.xml

<!-- 配置镜像加速 -->
<mirror>
<id>alimaven</id>
<name>alimaven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>

注意:如果配置本地仓库的地址,一定要赋予该目录其他用户可读写的权限

rsync安装

跨机器文件同步

1
yum install rsync -y
关闭防火墙
1
2
3
systemctl stop firewalld
# 永久关闭
systemctl disable firewalld
Jenkins安装
1
2
3
4
5
6
mkdir /opt/jenkins
# 为jenkins用户授权
chown -R jenkins:jenkins /opt/jenkins
su jenkins
# 下载Jenkins最新版
axel -n 20 http://mirrors.jenkins-ci.org/war/latest/jenkins.war

创建启动脚本 vi jenkins.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/bin/bash
args=$1
#注意修改jenkinswar包的目录
jenkins_war_path="/opt/jenkins"
#jenkins开放端口
jenkins_http_port="8888"
#java安装路径
java_home="/opt/jdk1.8.0_301"
function isRuning(){
local jenkinsPID=`ps -ef|grep jenkins.war|grep -v grep|awk '{print $2}'`
if [ -z ${jenkinsPID} ];then
echo "0"
else
echo ${jenkinsPID}
fi
}

#停止jenkins
function stop(){
local runFlag=$(isRuning)
if [ ${runFlag} -eq "0" ];then
echo "Jenkins is already stoped."
else
`kill -9 ${runFlag}`
echo "Stop jenkins success."
fi
}

#启动jenkins
function start(){
local runFlag=$(isRuning)
echo "${runFlag}"
if [ ${runFlag} -eq "0" ];then
`${java_home}/bin/java -jar ${jenkins_war_path}/jenkins.war --httpPort=${jenkins_http_port} &` > /dev/null
if [ $? -eq 0 ];then

echo "Start jenkins success."
exit
else
echo "Start jenkins fail."
fi
else
echo "Jenkins is running now."
fi
}

#重启jenkins
function restart(){
local runFlag=$(isRuning)
if [ ${runFlag} -eq "0" ];then
echo "Jenkins is already stoped."
exit
else
stop
start
echo "Restart jenkins success."
fi
}

#根据输入的参数执行不同的动作
#参数不能为空
if [ -z ${args} ];then
echo "Arg can not be null."
exit
#参数个数必须为1个
elif [ $# -ne 1 ];then
echo "Only one arg is required:start|stop|restart"
#参数为start时启动jenkins
elif [ ${args} = "start" ];then
start
#参数为stop时停止jenkins
elif [ ${args} = "stop" ];then
stop
#参数为restart时重启jenkins
elif [ ${args} = "restart" ];then
restart
else
echo "One of following args is required: start|stop|restart"
exit 0
fi

启动jenkins

1
sh jenkins.sh start

配置加速

vi /home/jenkins/.jenkins/hudson.model.UpdateCenter.xml
将xml的url替换为http://mirror.xmission.com/jenkins/updates/update-center.json

1
2
3
4
5
6
7
<?xml version='1.1' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>http://mirror.xmission.com/jenkins/updates/update-center.json</url>
</site>
</sites>

或在web界面manage jenkins->manage plugins->Advanced->Update Site修改

访问Jenkins http://ip:8888

获取管理员密码 cat /home/jenkins/.jenkins/secrets/initialAdminPassword

修改管理员密码

页面左上角jenkins->people->admin->configure修改

插件安装

需要安装ssh、Pipeline、Role-based Authorization Strategy、Git、Gitee、Git Parameter、thinBackup这些插件,manage jenkins->manage plugins,点击available搜索下载

插件名 作用
Pipeline 流水线部署项目
Role-based Authorization Strategy 基于角色的用户管理权限策略,最常用的jenkins权限策略管理插件
Git 支持使用GitHub、gitlab等源码仓库,创建普通job会用到
Gitee Gitee基于gitlab plugin开发的插件,配置Jenkins触发器,接受gitee平台发送的webhook触发jenkins进行自动化持续集成或持续部署,并可将构建状态反馈gitee平台
Git Parameter 可把git的tag branch作为构建参数传进来,方便使用branch构建
Extended Choice Parameter 参数化构建
Maven Integration 为maven2/3项目提供高级集成功能
SonarQube Scanner 代码扫描
Email Extension 扩展发送告警邮件的控制粒度,可定义邮件触发器、邮件内容、收件人
Workspace Cleanup 每次build之前删除workspace目录下指定的文件
Monitoring 监控Jenkins节点的CPU、系统负载、平均响应时间和内存使用
Build Monitor View 将Jenkins项目以看板的形式呈现
ThinBackup 单元测试覆盖率
jacoco 单元测试覆盖率
Generic Webhook Trigger webhook

jenkins的配置

Jenkins->Manage jenkins->Configure System 进入系统配置页面

令牌配置

证书令牌

分组管理

new view即可

备份恢复

存储结构

在Jenkins中,所有数据默认都以文件形式存储在$JENKINS_HOME目录。占用空间最大的是jobs目录和workspace目录。

  • jobs目录 项目在Jenkins上的配置、构建日志、构建结果等所在的目录,对应/home/jenkins/.jenkins/jobs目录
  • workspace目录 项目在Jenkins上配置的源码仓库地址下载的源码所在的目录,如Java的maven构建操作就在此目录,对应/data/jenkins_data/workspace目录

结构

  • jobs目录下的一级目录名,均为在Jenkins上新建的job名称
  • 二级目录下的build里面出现的数字1,2,3…表示构建历史所在的目录,也就是一次构建有一个构建版本号,从1开始随着构建次数增加递增
  • workspace下面都是空的,因为还没拉取代码

pipeline

概念

流水线即代码,流水线的代码定义了整个的构建过程,包括构建、测试和交付应用程序的阶段,对流水线的定义写在Jenkinsfile文件中,可被提交到源码仓库。创建Jenkinsfile并提交到源码仓库的好处:

  • 自动为所有分支创建流水线构建过程并拉取请求
  • 在流水线上代码复查/迭代
  • 对流水线进行审计跟踪
  • 该流水线的真正源码可被团队成员共同查看和编辑

语法支持

Jenkinsfile能用两种语法编写,都能用pipeline内置的插件和steps

  • Declarative Pipeline声明式 推荐使用,语法更严格,有固定的组织结构
  • Scripted Pipeline 脚本化 groovy语言,只对结构和语法限制,用户可自己灵活实现和扩展

基本概念

  • stage 阶段,一个pipeline分为多个stage,每个stage代表一组操作,stage是一个逻辑分组的概念,可跨多个node
  • node 构建服务器节点,一个node就是要给Jenkins节点,或master或agent,是执行step的具体运行期环境
  • step 步骤,最基本的操作单元,小到创建一个目录,大到构建一个docker镜像,由各类Jenkins plugin提供

pipeline语法

参考https://www.jenkins.io/zh/doc/book/pipeline/syntax/#declarative-directives

  • 声明式pipeline必须包含在固定格式的pipeline{}内

    1
    2
    3
    pipeline {
    // insert declarative pipeline here
    }
  • 每个声明语句必须独立一行,行尾无需分号

  • 块blocks 由大括号括起来的语句如 pipeline{} parameters{} script{}

  • 章节sections 通常包括一个或多个指令或步骤 如agent post stages steps

  • 指令directives 如 environment option parameter trigger stage tools when

  • 步骤step 执行脚本式pipeline,如script{}

  • 块只能由阶段stages{}、指令、步骤(steps{})或赋值语句组成

  • 属性引用语句被视为无参方法调用,如input()

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// jenkinsfile(declarative pipeline)
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make'
echo "Hello world"
script {
def browsers = ['chrome','firefox']
for (int i = 0; i < browsers.size(); ++i) {
echo "Testing the ${browsers[i]} browser"
}
}
}
}
stage('Test') {
steps {
sh 'make check'
junit 'reports/**/*.xml'
}
}
stage('Deploy') {
steps {
sh 'make publish'
}
}
}
}

说明:

  • pipeline是声明式流水线的一种特定语法,定义了包含执行整个流水线的所有内容和指令的block
  • agent 指示Jenkins为整个流水线分配一个执行器(在Jenkins环境中的任何可用代理/节点上)和工作区。一般用作指定在哪个节点上构建,如果不指定就写any表示任意节点
  • 定义Build Test Deploy三个阶段,每个阶段执行不同的步骤
  • stage 描述stage of this pipeline的语法块,定义在pipeline的不同阶段
  • steps 声明式流水线的特定语法,描述这个stage中要运行的步骤
  • sh 是一个执行给定的shell命令的流水线
  • echo 简单的字符串到控制台输出
  • junit 另一个聚合测试报告的流水线
  • stage括号的值表示阶段名称,内容不固定,自定义即可

gitee的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
pipeline {
//指定运行的节点,slave运行
agent {
node {
label 'follower'
}
}
//定义各个阶段
stages {
// 获取git代码,credentialsId是配置的git账号
stage('git') {
steps {
git credentialsId: 'gitee-username', url: 'xxx'
}
}
// test阶段
stage('test') {
steps {
//可从environment获取
sh '${MAVEN_HOME}/bin/mvn test'
}
}
// 编译打包阶段,打jar包
stage('build') {
steps {
sh '${MAVEN_HOME}/bin/mvn clean package -DskipTests'
}
}
// docker镜像
stage('docker') {
steps {
sh 'docker build -t jenkins-demo:${BUILD_NUMBER} .'
}
}
// 部署阶段,停掉旧的docker进程,启动新镜像
stage('deploy') {
steps {
//如果test-boot没有在启动中,如果不跟或语句,会执行失败
sh 'docker rm -f jenkins-demo || sleep 0'
sh 'docker run --name jenkins-demo -d -p 8080:8080 jenkins-demo:${BUILD_NUMBER}'
}
}
}
}

其他常见语法:

  • when 允许流水线根据给定的条件决定是否应该执行阶段,必须包含至少一个条件

  • environment

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    pipeline {
    environment {
    // 顶层流水线块中使用的 environment 指令将适用于流水线中的所有步骤
    }
    stages {
    stage('Example') {
    environment {
    //在一个 stage 中定义的 environment 指令只会将给定的环境变量应用于 stage 中的步骤
    AN_ACCESS_KEY = credentials('my-prefined-secret-text')
    }
    steps {
    sh 'printenv'
    }
    }
    }
    }
  • options 从流水线内部配置特定于流水线的选项,如超时,中止流水线

    1
    2
    3
    4
    5
    6
    7
    pipeline { 

    options {
    timeout(time: 30, unit: 'SECONDS') //unit根据需要可选值有:SECONDS、MINUTES、HOURS
    retry(2)
    }
    }

《秦吏》有感

《秦吏》虽然是一个穿越小说,然而深厚的历史积淀,以及现代人的代入感,让人更能融入2千年前那赳赳老秦的画面。

昔日我们读书时,”秦王奋六世之余烈“这话并没有如何的感同身受,但是扪心自问,如果你的家族,从你开始,不说六代人,一般两三代就已经可以到寒门,跨越阶层了吧。如果六代人一直奋勇进取,不能扫六合才怪。这是中华的气运呀。

跑步随想

前言

原文本想发表到CSDN,但是牢骚话发不了,说什么违反社区规定,索性才开了自己的博客,专门记录自己的人生感悟和技术整理。

随想

当时日期为2023-05-31

最近看到或者亲身感受到好几个消息,还是有些担忧的。

  1. 卫健委开始呼吁一孩补贴了
  2. 《中国慈善家》根据基层医院的数据估算出生人口低于800万 而2022年为956万,2016年为1786万…
  3. 2022年人口首次下降
  4. 自己公司延长一个小时下班

其实陷入了一个死亡螺旋。

  • 要年轻人生孩子->鼓励结婚
    • ->鼓励谈恋爱->有更多的空闲时间
      
    • ->需要买房->要有合适的房价
    • ->生育年龄要尽早->初婚年龄要合适

而现实:

  • 构建营商环境->企业的残酷竞争和漠视法律

    • ->996已经是普遍情况->年轻人没时间谈恋爱
      
    • ->下行周期内卷更严重->学历内卷->被迫大龄单身->初婚年龄已不适合要孩子
  • 持续多年的土地财政->房价高企,经济下行周期更没人买账

    • 不敢降价,否则银行全是不良资产
      
    • 不敢放开,更严重,更无法收拾
    • 只能继续高企->年轻人继续买不起房,下行周期断供、烂尾->结不起婚

希望小伙伴们都能早日穿过幽暗的岁月,向前看,发出属于自己的光,温暖自己和周围。

后续

自己会关注这些烂事,当然,由于自己尚在生存关,仅仅只记录,防止国内互联网没了记忆,欢迎拍砖。

英语规划

前言

英语,作为一个拦路虎,很烦人,但是又不得不面对,因为英语作为国际通用语言,比较前沿的东西都是英语写的,科研成果、github、chatGPT等等,而中文环境充斥着僵尸号、各种二三手的消息、自我阉割的审查机制,导致学习成本用人生长度来计算的话,比英语高多了。

而作为成年人,尤其是职场人,你很难跟学生一样,抽出专门的时间去学习,因此,学习的目的是什么?如何有效学习?需要我们深度思考。

学习英语的目的

最通俗的来说,就是升职加薪。作为程序员,各类语言、工具基本上都是英语的,写的代码、甚至注释,最好也是英语,减少歧义,让自己的代码和轮子被复用,增强自己的影响力。

此外,最新的思想、潮流,基本上都是大洋彼岸吹来的,必须跟上潮流才能端好饭碗。如果有机会参与Google、Amazon、Facebook等的开源项目甚至加入这些公司,那就是世界级的程序员,不用再考虑生存价值了。

如何有效学习

现阶段不需要会说,只需要会读写即可。毕竟大部分是博客、论文交流。

以2年为期,让自己彻底学会读写。

读写的训练

一个是读,也就是单词和短语。因为程序世界的会短语即可,不需要太过复杂。

工具有:

  • 谷歌翻译 最简单
  • Burning Vocabulary 插件,遇到不认识的,放到行囊中,上下班的地铁上再多认识几遍即可

要以我为主,在两年后彻底摆脱谷歌翻译为目标。

材料 TODO

有空列清单,碰到就放这里,方便查看。

PS

看到一个安卓大佬的GitHub,https://github.com/JakeWharton

我竟然沉默无言了,确实”talk is cheap, show me your code.”,希望激励下自己。

image-20230618161203722

2023年6月29日

今天刷YouTube,发现自己关注的一个博主被裁员,然后很快就又找到一个新公司,晒他的leetcode,原来这么努力啊。

image-20230629224912979

感觉真的是自己时来运转,发现了自己之前连努力都达不到…难怪这么水,💪

  • Copyrights © 2023-2025 Larry Wang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信