开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Spring循环依赖解析

发表于 2021-11-29

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

当开发中出现类似报错 :
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException
就是项目中出现循环依赖了,项目中的处理方式一般是将公共调用的功能放到另一个文件,避免循环引用.那spring 是怎么解决这类问题的呢。A引用B,B引用A的

使用场景:

依赖情况 依赖注入方式 循环依赖是否被解决
AB相互依赖(循环依赖) 均采用setter方法注入 是
AB相互依赖(循环依赖) 均采用构造器注入 否
AB相互依赖(循环依赖) A中注入B的方式为setter方法,B中注入A的方式为构造器 是
AB相互依赖(循环依赖) B中注入A的方式为setter方法,A中注入B的方式为构造器 否

从上面的测试结果我们可以看到,不是只有在setter方法注入的情况下循环依赖才能被解决,即使存在构造器注入的场景下,循环依赖依然被可以被正常处理掉。

spring 是怎么解决的:

提到循环依赖,大脑中最先想到的是”三级缓存”,”提前暴露”…balabala 至于里面的流程和细节就已经忘的差不多了,下面梳理整个流程,方便加深记忆

Spring通过三级缓存解决了循环依赖,使用了三个MAP:

其中一级缓存为单例池(singletonObjects),一级缓存,存放的是单例 bean 的映射。注意,这里的 bean 是已经创建完成的。

二级缓存为早期曝光对象earlySingletonObjects, 存放的是早期半成品(未初始化完)的 bean,对应关系也是 bean name --> bean instance。 它与 {@link #singletonObjects} 区别在于, 它自己存放的 bean 不一定是完整

三级缓存为早期曝光对象工厂(singletonFactories)。
三级缓存,存放的是 ObjectFactory,可以理解为创建早期半成品(未初始化完)的 bean 的 factory

  • 当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
  • 当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,
  • 第一步,先获取到三级缓存中的工厂;
  • 第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。
  • 紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

当初就是靠着这图让我搞清了一直不愿意看的循环依赖:

图片.png
特征:
1.先调用的先完成初始化
2.先三后二最后一级
3.最后二级为空

源码解读

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
kotlin复制代码// DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从单例缓冲中加载 bean
Object singletonObject = this.singletonObjects.get(beanName);
// 缓存中的 bean 为空,且当前 bean 正在创建
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 加锁
synchronized (this.singletonObjects) {
// 从 earlySingletonObjects 获取
singletonObject = this.earlySingletonObjects.get(beanName);
// earlySingletonObjects 中没有,且允许提前创建
if (singletonObject == null && allowEarlyReference) {
// 从 singletonFactories 中获取对应的 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 获得 bean
singletonObject = singletonFactory.getObject();
// 添加 bean 到 earlySingletonObjects 中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从 singletonFactories 中移除对应的 ObjectFactory
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
  • 首先,从一级缓存 singletonObjects 获取。
  • 如果,没有且当前指定的 beanName 正在创建,就再从二级缓存 earlySingletonObjects 中获取。
  • 如果,还是没有获取到且允许 singletonFactories 通过 #getObject() 获取,则从三级缓存 singletonFactories 获取。如果获取到,则通过其 #getObject() 方法,获取对象,并将其加入到二级缓存 earlySingletonObjects 中,并从三级缓存 singletonFactories 删除

上面是从缓存中获取,但是缓存中的数据从哪里添加进来的呢?

1
2
3
4
5
6
7
8
9
10
11
12
kotlin复制代码// DefaultSingletonBeanRegistry.java

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

从这段代码我们可以看出,singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的诀窍所在。同时这段代码发生在 #createBeanInstance(...) 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了。

现三级缓存 singletonFactories 和 二级缓存 earlySingletonObjects 中的值都有出处了,那一级缓存在哪里设置的呢

1
2
3
4
5
6
7
8
9
10
csharp复制代码/ DefaultSingletonBeanRegistry.java

protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}

添加至一级缓存,同时从二级、三级缓存中删除。

补充:

为什么Sping不选择二级缓存方式,而是要额外加一层缓存?

如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【计算机图形学】交互式Bezier曲线——MATLAB GU

发表于 2021-11-29

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

1 引言

Bezier样条曲线由皮埃尔∙贝塞尔发明,最初为雷诺汽车公司设计汽车车身开发的。

后来发现其有着很多特殊的性质, 在图形设计和路径规划等方面应用十分广泛

本文将基于MATLAB GUI编程,实现可交互式三次贝赛尔曲线

2 思路

2.1 原理

给定空间n+1个点,Bezier曲线段的参数方程表示为p(t)=∑i=0nPiBi,n(t)t∈[0,1]p(t)=\sum_{i=0}^{n} P_{i} B_{i, n}(t) \quad t \in[0,1]p(t)=∑i=0n​Pi​Bi,n​(t)t∈[0,1],称为n次Bezier曲线

Bi,n(t)\mathrm{B}_{\mathrm{i}, \mathrm{n}}(\mathrm{t})Bi,n​(t)是Bernstein基函数,有如下形式:

Bi,n(t)=n!i!(n−i)!ti(1−t)n−i=Cniti(1−t)n−i(i=0,1,….n)B_{i, n}(t)=\frac{n !}{i !(n-i) !} t^{i}(1-t)^{n-i}=C_{n}^{i} t^{i}(1-t)^{n-i} \quad(i=0,1, \ldots . n)Bi,n​(t)=i!(n−i)!n!​ti(1−t)n−i=Cni​ti(1−t)n−i(i=0,1,….n)

2.2 Bezier函数编写思路

根据Bezier曲线参数方程的形式,函数主要思想为三个矩阵相乘,即参数矩阵·系数矩阵·控制点矩阵

  1. 参数矩阵:[t3t2t1]\begin{bmatrix}t^3&t^2&t&1\end{bmatrix}[t3​t2​t​1​]
  2. 系数矩阵:[−13−313−630−33001000]\begin{bmatrix}-1&3&-3&1\3&-6&3&0\-3&3&0&0\1&0&0&0\end{bmatrix}⎣⎡​−13−31​3−630​−3300​1000​⎦⎤​
  3. 控制点矩阵:
* xx=[p0(1)p1(1)p2(1)p3(1)]xx=\begin{bmatrix}p\_0(1)&p\_1(1)&p\_2(1)&p\_3(1)\end{bmatrix}xx=[p0​(1)​p1​(1)​p2​(1)​p3​(1)​]
* yy=[p0(2)p1(2)p2(2)p3(2)]yy=\begin{bmatrix}p\_0(2)&p\_1(2)&p\_2(2)&p\_3(2)\end{bmatrix}yy=[p0​(2)​p1​(2)​p2​(2)​p3​(2)​]

Tips:

需注意的是,参数变化从0到1,在计算机中只能离散化实现

故绘制Bezier曲线时,也需循环为每一个点的x,y坐标赋值,即每一个点对应一个不同的t参数值

2.3 交互式实现思路

  1. 首先在MATLAB中新建一个GUIDE,即基于图窗的App;保存后将自动生成.fig文件和.m文件
* .fig文件:界面文件
* .m文件:源程序文件
  1. 在.fig文件中搭建界面,将主要控件拖动至图窗
  2. 在对应控件的Callback事件中编写相应的方法
  3. 可直接使用[F5]运行,或在文件目录中双击.fig文件

3 过程

3.1 界面搭建

由于Bezier曲线与坐标轴密切相关,故新建项目时选择了“GUI with Axes and Menu”

3.2 三次Bezier函数

bezier(p0,p1,p2,p3)输入参数为控制点,矩阵形式

首先初始化系数矩阵和控制点矩阵

再以0.001为步长,循环计算三次Bezier曲线上点坐标

1
2
3
4
5
matlab复制代码for t=0:0.001:1
tt=[t^3 t^2 t 1]*a;
x(end+1)=tt*xx';
y(end+1)=tt*yy';
end

最后绘制控制多边形和三次Bezier曲线

3.3 回调函数主要部分

1
2
3
4
5
6
matlab复制代码function pushbutton2_Callback(hObject, eventdata, handles)
handles.x1=str2num(get(handles.edit1,'String'));
handles.y1=str2num(get(handles.edit2,'String'));
%...
bezier([handles.x1,handles.y1],[handles.x2,handles.y2],...
[handles.x3,handles.y3],[handles.x4,handles.y4]);

此事件为“绘制”按钮的回调事件,首先将文本框中输入的字符型x,y坐标转为整型,再调用三次Bezier函数,即可实现功能

完整代码请见Bezier3(gitee.com)

4 结果

在文本框中输入控制点的x,y坐标,点击“绘制”按钮,即可在坐标框中绘制得到三次Bezier曲线及其控制多边形

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

消息队列的实现

发表于 2021-11-29

这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

mq消息的优点大家都知道,可以削峰和解耦,减缓高峰流量,降低系统压力,减少并发问题,将业务功能执行拆解开来。

实现系统之间的并行执行:

比如中台服务中,不同的中台服务之间可以通过异步消息的方式进行信息传递,而且可以不需要考虑上下游业务的处理结果,也不受上下游业务结果的影响。当然也可以自己生产消息自己消费。

解耦的场景比如说,两个有关联的功能逻辑,买票系统和车票库存系统,当下单买票时,车票库存系统出现故障,两个系统之间通过消息队列进行关联,则库存系统的异常不会影响买票下单的正常进行,因为系统之间是通过异步消息去通知库存系统进行车票库存的增减。

下面用一个实际例子展示消息队列的实现。

消息生产者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码/**
* 消息生产者,可以是在业务逻辑方法中进行调用,比如工单创建完成的消息,后续进行异步消费用的
* 当然消息也可以是延迟发送的
*
*
*/
private void orderPassMessage(TestDTO testdto, MQTypeEnum MqType) {
  Map params = Maps.newHashMapWithExpectedSize(6);
  params.put("MqType", MqType);
  params.put("Code", "");
  params.put("id", "123456jdiwuidnkqwdnkajsd");
  params.put("user", "MyTest");
​
  MqTestDemoMessage.sendMessage(params);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码/**
* 封装好的消息体内容
*/
public static void sendMessage(Map params) {
//消息中一定要包含时间
  Date date = new Date();
  Map headers = MessageHeaders.headers()
//要设置一个有一定的业务含义的Tag,作为消息监控方在消费消息时的依据
//还要传入一些必要的业务参数
          .setTag("Test_TAG_DEMO_ONE")
          .setCode("mq_test_code")
          .setTime(date)
          .setOutId("99999")
          .setDelay(MessageProducer.FIVE_SECONDS)
          .build();
  params.put("date", DateFormatUtils.format(date, TestDateUtils.DATE));
 
  log.info("TestMqMessage sendMessage to other object ,headers={},params={}", headers,params);
  producer.sendMessage(JSONObject.toJSONString(params), headers);
}
消息消费者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码/**
* 消费方
* Code=mq_test_biz_code, x-delay=5000, tag=Test_TAG_DEMO_ONE
* @param params
* @param headers
* 消息队列的名称TestMqDemoSink,如果消息消费失败,会进入死信队列
*/
@StreamListener(value = TestMqDemoSink.INPUT, condition = "headers['tag']=='Test_TAG_DEMO_ONE' OR headers['Code']=='mq_test_code'")
//这里就是监控消息的依据,根据headers里面的tag和bizCode来进行消息的过滤,两个条件的或的关系,当然也可以是且的关系。
public void MqTestSendMessage(@Payload String params, @Headers MessageHeaders headers) {
//日志打印一下接收到的消息体内容
  log.info("MqTestSendMessage params: {}, headers: {}", params, headers);
​
  MessageHeaders headersTest = new MessageHeaders(headers);
  JSONObject eventParams = JSON.parseObject(params);
  BaseResponse response = TestProService.execute(eventParams);
  if (!ResponseCodeEnum.SUCCESS.equals(response.getCode())) {
      log.error(" test MqDemoTest error response:{}", JSON.toJSONString(response));
      return;
  }
}
1
2
3
4
5
6
7
java复制代码//指定的消息队列
public interface TestMqDemoSink {
  String INPUT = "testMqDemoInput";
​
  @Input(TestMqDemoSink.INPUT)
  SubscribableChannel input();
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

《Elasticsearch核心技术与实战》笔记 -- 第三

发表于 2021-11-29

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

视频课程:极客时间 – 《Elasticsearch核心技术与实战》– 配套代码 GitHub

系列文章:

《Elasticsearch核心技术与实战》笔记 – 第一章:概述

《Elasticsearch核心技术与实战》笔记 – 第二章:安装上手

《Elasticsearch核心技术与实战》笔记 – 第三章:ElasticSearch 入门(1)

《Elasticsearch核心技术与实战》笔记 – 第三章:ElasticSearch 入门(2)

URI Search 详解

参考:大神都这么做,让 Kibana 搜索语法 query string 也能轻松上手

URI Search - 通过 URI query 实现搜索

1
2
3
4
json复制代码GET  /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=10&timeout=1s
{
"profile": true
}
  • q 指定查询语句,使用 Query String Syntax
  • df 默认字段,不指定时,会对所有字段进行查询
  • sort 排序
  • from 和 size 用于分页
  • profile 可以查看查询是如何被执行的

Query String Syntax(1)

  • 指定字段 v.s 泛查询
+ q=title:2012 / q=2012

1、df查询.png
2、泛查询.png
3、指定字段.png

  • Term v.s Phrase
+ Beautiful Mind 等效于 Beautiful OR Mind
+ “Beautiful Mind”,等效于 Beautiful AND Mind。Phrase 查询,还要求前后顺序保持一致
  • 分组与引号
+ title:(Beautiful AND Mind)
+ title="Beautiful Mind"

4、Phrase查询.png
一、termQuery+泛查询.gif
一、分组bool查询.gif

Query String Syntax(2)

  • 布尔操作
+ AND / OR / NOT 或者 && / || / !
    - 必须大写
    - title:(matrix NOT reloaded)
  • 分组
+ +表示must
+ -表示 must\_not
+ title:(+matrix - reloaded)

5、and分组查询.png
6、not分组查询.png
7、+号分组查询.png

Query String Syntax(3)

  • 范围查询
+ 区间表示:[]闭区间,{}开区间
    - year:{2010 TO 2018} 意为 2010 < year< 2018
    - year:[\* TO 2018] 意为 year<=2018
  • 算法符号
+ year:>2010 意为查找2010年以后的电影(不包含2010年)
+ year:(>2010 AND <=2018) 意为查找2010年至2018年的电影(不包含2010年且包含2018年)
+ year:(%2B>2010 AND %2B<2018) 意为查找2010年至2018年的电影(不包含2011年且不包含2018年)

思考:在kibana中无法输入{}作为查询条件?

8、范围查询.png

Query String Syntax(4)

  • 通配符查询(通配符查询效率低,占用内存大,不建议使用,特别是放在最前面
    • ? 代表1个字符 ,* 代表0个或多个字符
      • title:mi?d
      • title:be*

9、通配符查询.png

  • 正则表达,这里并不支持所有的正则语法,使用的时候要注意查看官方文档说明。另外正则查询的内存压力也很大,要谨慎使用
    • title:/[bt]oy/

10、正则匹配.png

  • 模糊匹配与近似查询
    • title:befutifl~1
    • title:”lord rings”~2

11、模糊匹配.png

RequestBody 与 QueryDSL 简介

Request Body Search

  • 将查询语句通过 HTTP Request Body 发送给 ElasticSearch
  • Query DSL(Domain Specific Language 查询表达式)
  • 高阶语法只能通过 Request Body Search 来实现,建议使用 Request Body Search 实现查询功能

12、Request Body Search.png

  • ignore_unavailable :如果URL中一个或多个索引不存在的时候,ignore_unavailable 表示是否忽略这些索引,值为true和false;
  • profile: 是否打开性能分析;
  • match_all :匹配所有;

分页查询

  • From 从 0 开始,默认返回 10 个结果
  • 获取靠后的翻页成本较高

13、分页查询.png

排序

  • 最好在 “数字型” 与 “日期型” 字段上排序
  • 因为对于多值类型或分析过的字段排序,系统会选一个值,无法得知该值

14、排序.png

_source filtering

  • 过滤你要查询的字段信息
  • 如果 _source 没有存储,那就只返回匹配文档的元数据
  • _source 支持使用通配符

_source[“name*“,”desc*“]

脚本字段

  • 通过 painless 脚本,计算你所需要的新字段
  • 用例:订单中有不同的汇率,需要结合汇率对订单价格进行排序

15、脚本字段.png

使用查询表达式 - Match

  • match 默认使用 OR 连接查询

16、match默认查询.png

  • 添加 operator 属性,改变单词连接方式

17、match-operator.png

使用查询表达式 - Match_Phrase

match_phrase查询分析文本并根据分析的文本创建一个短语查询。match_phrase 会将检索关键词分词。match_phrase的分词结果必须在被检索字段的分词中都包含,而且顺序必须相同,而且默认必须都是连续的。

区别与match:match模糊匹配,先对输入进行分词,对分词后的结果进行查询,文档只要包含match查询条件的一部分就会被返回。

match_phrase 核心参数:slop 参数-Token之间的位置距离容差值

18、match_phrase查询.png

QueryString & SimpleQueryString 查询

Query String Query

  • 类似URI Query

19、query string.png

Simple Query String Query

20、simple query string.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

centos7 minimal网络配置 结束语

发表于 2021-11-29

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

##前言

在使用VMware Oracle VM 等虚拟机软件,新建的Centos7系统的时候,我们很可能忘记了配置网络或者发现网络不管用了,尤其是无界面版本看着黑黑的界面没有网络,这时候就比较迷茫,这里带给大家两种方式进行配置网络.

nmcli方式

minimal有奇效

查看网卡信息

1
sql复制代码nmcli connection show

我的响应

1
2
复制代码名称   UUID                                  类型            设备  
ens33 1cc6474a-93d3-47d0-a54c-f1ef06de5dd6 802-3-ethernet ens33

在这里我的设备的名称是ens33

配置自动获取IP地址

1
arduino复制代码nmcli connection modify ens33   connection.autoconnect yes   ipv4.method auto

因为命令比较长, 你也可以换行输入,效果相同

1
2
3
markdown复制代码nmcli connection modify ens33 \ 
> connection.autoconnect yes \
> ipv4.method auto

重启网络服务

1
复制代码systemctl  restart network

查看网络信息

1
sql复制代码ip addr 或者指定网卡 ip addr show ens33

我的ip addr响应

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:0c:29:14:e5:cc brd ff:ff:ff:ff:ff:ff
inet 192.168.119.128/24 brd 192.168.119.255 scope global dynamic ens33
valid_lft 1797sec preferred_lft 1797sec
inet6 fe80::6f8f:2512:1c71:841a/64 scope link
valid_lft forever preferred_lft forever

到这里发现ens33有了ip地址, 你就可以ping一下访问外网了

nmtui方式

使用TAB选择功能区域,使用方向键← → ↑ ↓ 选择, 按ENTER键确认 ,按Esc返回

输入nmtui进入图形界面

nmtui

image.png

向下选择Edit a connection 并确认

这个服务器的网卡信息是enp0s3

image.png

编辑现有网络连接

选择到enp0s3 按ENTER 或者选择<Edit...> 编辑网络信息

image.png

这里根据需要编辑信息

此处选择的是IPv4动态获取,IPv6动态获取

完成后选择<OK>保存 或者<Cancel>取消

添加一个网络连接

image.png

选择Ethernet后按照上面的图配置,或者自定义静态地址.这里请自行百度细节

1
复制代码systemctl  restart network

修改network-script下配置文件

查看网卡信息

1
sql复制代码nmcli connection show

我的响应

1
2
3
4
5
6
7
8
9
10
11
12
13
sql复制代码1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:0a:0b:17 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.110/24 brd 192.168.0.255 scope global noprefixroute dynamic enp0s3
valid_lft 2420sec preferred_lft 2420sec
inet6 240e:345:7505:5801:c394:f3ad:8c97:7211/64 scope global noprefixroute dynamic
valid_lft 86317sec preferred_lft 14317sec
inet6 fe80::2b94:1d60:dc71:4a25/64 scope lin

此处网卡信息是enp0s3

修改网络配置

前往/etc/sysconfig/network-script/下 找到ifcfg-enp0s3文件(ifcfg-开头+你的网卡名称)
将onboot=no 改成yes

我的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ini复制代码TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=enp0s3
UUID=57827aa4-1133-4675-9d3b-2534dce99400
DEVICE=enp0s3
ONBOOT=yes

重启网络服务

1
复制代码systemctl  restart network

结束语

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

如果您喜欢我的文章,可以[关注]+[点赞]+[评论],您的三连是我前进的动力,期待与您共同成长~

1
2
3
4
arduino复制代码    作者:ZOUZDC
链接:https://juejin.cn/post/7028963866063306760
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

36数据结构-队列的概念与实现(上)

发表于 2021-11-29

一.队列的概念与与特性

1.1 队列的概念

队列是一种特殊的线性表,仅仅可以再线性表的两端进行操作,为队头队尾

  • 队头(front):取出的数据的一端
  • 队尾(rear):插入数据的一端

1.2 队列的特性

队列的特性先进先出,类似于生活中的排队
在这里插入图片描述

二.队列的实现要点

2.1 队列的顶层父类实现

Queue.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
c复制代码#ifndef QUEUE_H
#define QUEUE_H

#include "Object.h"

namespace DTLib
{

template < typename T >
class Queue : public Object
{
public:
virtual void add(const T& e) = 0;//入队列
virtual void remove() = 0;//出队列
virtual T front() const = 0;//队头元素
virtual void clear() = 0;//清空队列
virtual int length() const = 0;//队列的长度
};

}

#endif // QUEUE_H

2.2 队列的继承关系

队列分为静态队列和链式队列,继承顶层父类即可
在这里插入图片描述

2.3 静态队列的实现要点

  • 使用原生数组作为队列的存储空间
  • 使用模板参数作为队列的最大容量
  • front是队头标识
  • rear表示队尾标识
    在这里插入图片描述

三.静态队列的接口实现

3.1 初始化

1
2
3
4
5
6
c复制代码staticQueue()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}

3.2 入队列

先判断队列长度是否大于容量,没有则在队尾标识的位置出插入元素,队尾标识往后移一位

1
2
3
4
5
6
7
8
9
10
11
12
13
c复制代码void add(const T& e)
{
if (m_length < N)
{
m_space[m_rear] = e;
m_rear = (m_rear + 1) % N;
m_length++;
}
else
{
cout << "not space to add" << endl;
}
}

3.3 出队列

先判断队列是否有元素,如果有,那么直接将队头m_front指向的元素加1,表示队头元素被移出队列

1
2
3
4
5
6
7
8
9
10
11
12
c复制代码void remove()
{
if (m_length > 0)
{
m_front = (m_front + 1) % N;//标识m_front指向的元素出队列了
m_length--;
}
else
{
cout << "not elem to remove" << endl;
}
}

3.4 获取队头元素

1
2
3
4
5
6
7
8
9
10
11
c复制代码T front() const
{
if (m_length > 0)
{
return m_space[m_front];
}
else
{
cout << "not elem to get" << endl;
}
}

3.5 清空操作/队列长度

1
2
3
4
5
6
7
8
9
10
11
12
13
c复制代码void clear()
{
if (m_length > 0)
{
m_front = 0;
m_rear = 0;
}
}

int size()
{
return m_length;
}

3.6 队满

表示长度为N,由于是循环计数所以还需要加上队头与队尾相等

1
2
3
4
c复制代码bool full()
{
return (m_length == N) && (m_front == m_rear);
}

3.7 队空

1
2
3
4
c复制代码bool empty()
{
return (m_length == 0)&&(m_front == m_rear);
}

四.完整代码

Queue.h

1
2
3
4
5
6
7
8
9
10
11
c复制代码#pragma once
template<typename T>
class Queue
{
public:
virtual void add(const T& e) = 0;
virtual void remove() = 0;
virtual T front() const = 0;
virtual void clear() = 0;
virtual int size() = 0;
};

staticQueue.h

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
81
82
c复制代码#pragma once
#include "Queue.h"
#include <iostream>

using namespace std;

template<class T,int N>
class staticQueue :public Queue<T>
{
protected:
T m_space[N];
int m_front;
int m_rear;
int m_length;
public:
staticQueue()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}


void add(const T& e)
{
if (m_length < N)
{
m_space[m_rear] = e;
m_rear = (m_rear + 1) % N;
m_length++;
}
else
{
cout << "not space to add" << endl;
}
}

void remove()
{
if (m_length > 0)
{
m_front = (m_front + 1) % N;//标识m_front指向的元素出队列了
m_length--;
}
else
{
cout << "not elem to remove" << endl;
}
}

T front() const
{
if (m_length > 0)
{
return m_space[m_front];
}
else
{
cout << "not elem to get" << endl;
}
}

void clear()
{
if (m_length > 0)
{
m_front = 0;
m_rear = 0;
}
}

int size()
{
return m_length;
}

bool empty()
{
return (m_length == 0)&&(m_front == m_rear);
}

};

测试代码
staticQueue.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
c复制代码#include <iostream>
#include "staticQueue.h"

int main()
{
staticQueue<int, 5> queue;

for (int i = 0; i < 5; i++)
{
queue.add(i);
}

for (int i = 0; i < 5; i++)
{
cout << queue.front() << endl;
queue.remove();
}

return 0;
}

结果:

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

一文带你了解Mybatis设计模式之工厂模式 Mybatis

发表于 2021-11-29

这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

Mybatis设计模式之工厂模式

工厂模式概念

在Mybatis中⽐如SqlSessionFactory使⽤的是⼯⼚模式,该⼯⼚没有那么复杂的逻辑,是⼀个简单⼯⼚模式。

简单⼯⼚模式(Simple Factory Pattern):⼜称为静态⼯⼚⽅法(Static Factory Method)模式,它属于创建型模式。

在简单⼯⼚模式中,可以根据参数的不同返回不同类的实例。简单⼯⼚模式专⻔定义⼀个类来负责创建其他类的实例,被创建的实例通常都具有共同的⽗类。

例⼦:⽣产电脑

假设有⼀个电脑的代⼯⽣产商,它⽬前已经可以代⼯⽣产联想电脑了,随着业务的拓展,这个代⼯⽣产商还要⽣产惠普的电脑,我们就需要⽤⼀个单独的类来专⻔⽣产电脑,这就⽤到了简单⼯⼚模式。
下⾯我们来实现简单⼯⼚模式:

    1. 创建抽象产品类

我们创建⼀个电脑的抽象产品类,他有⼀个抽象⽅法⽤于启动电脑:

image.png

  • 2.创建具体产品类

接着我们创建各个品牌的电脑,他们都继承了他们的⽗类Computer,并实现了⽗类的start⽅法:

image.png

    1. 创建⼯⼚类

接下来创建⼀个⼯⼚类,它提供了⼀个静态⽅法createComputer⽤来⽣产电脑。你只需要传⼊你想⽣ 产的电脑的品牌,它就会实例化相应品牌的电脑对象

image.png

客户端调⽤⼯⼚类
客户端调⽤⼯⼚类,传⼊“hp”⽣产出惠普电脑并调⽤该电脑对象的start⽅法:

image.png

Mybatis体现

Mybatis中执⾏Sql语句、获取Mappers、管理事务的核⼼接⼝SqlSession的创建过程使⽤到了⼯⼚模式。

有⼀个SqlSessionFactory 来负责 SqlSession 的创建

image.png

SqlSessionFactory可以看到,该Factory的openSession ()⽅法重载了很多个,分别⽀持autoCommit、Executor、Transaction等参数的输⼊,来构建核⼼的SqlSession对象。

在DefaultSqlSessionFactory的默认⼯⼚实现⾥,有⼀个⽅法可以看出⼯⼚怎么产出⼀个产品:

image.png

这是⼀个openSession调⽤的底层⽅法,该⽅法先从configuration读取对应的环境配置,然后初始化TransactionFactory 获得⼀个 Transaction 对象,然后通过 Transaction 获取⼀个 Executor 对象,最后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession。至此Mybatis中使用工厂模式的方法已经说清楚,Mybatis还有很多地方也使用了M工厂模式,暂不一一分析。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

GO切片的删除-指针与非指针的区别

发表于 2021-11-29
  1. 非指针,删除元素,失败,并没有影响到源切片
1
2
3
4
5
6
7
8
9
10
11
12
go复制代码func TestDelOneElementFromSlice(t *testing.T) {
arr := []int{1,2,3,4}
fmt.Printf("test : %p\n",arr)
drop1(&arr)
fmt.Println(arr)
}

//非指针接收切片
func drop1(arr []int) {
fmt.Printf("drop1 : %p\n",arr)
arr = arr[:len(arr)-1]
}

运行结果:

图片.png

  1. 指针,删除元素,成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go复制代码func TestDelOneElementFromSlice(t *testing.T) {
arr := []int{1,2,3,4}
fmt.Printf("test : %p\n",arr)
drop2(&arr)
//swap(arr)
//drop1(arr)
fmt.Println(arr)
}

func drop2(arr *[]int) {
p := *arr
*arr = p[:len(p)-1]

fmt.Printf("drop2 : %p\n",arr)
fmt.Printf("drop2 p : %p\n",p)
}

运行结果:
图片.png

  1. 交换元素,无需指针也可成功
1
2
3
4
5
6
7
8
9
10
11
12
go复制代码func TestDelOneElementFromSlice(t *testing.T) {
arr := []int{1,2,3,4}
fmt.Printf("test : %p\n",arr)
//drop2(&arr)
swap(arr)
//drop1(arr)
fmt.Println(arr)
}

func swap(arr []int) {
arr[0],arr[1] = arr[1],arr[0]
}

运行结果:

图片.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

浅入浅出mybatis(四)

发表于 2021-11-29

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战。

前文简要的描述了mybatis的初始化部分,我们了解了mybatis是如何读取xml,加载配置,到解析sql完成操作的全过程,然后简要聊都了整个mybatis的结构层级。今天开始先从边缘模块开始,逐渐了解整个mybatis。第一个模块是exception包,这个几乎没有业务上的依赖,相对来说是最简单的。

首先并不是所有的异常类都在exception包中,exception包中只定义了父类和异常工厂方法。这里有一个小细节,通常我们都分包会有2种原则:

  • 按功能,同一功能类型的放一起
  • 按业务,做同一个业务的放一起

这两者各有利弊,纯粹的按照功能分,会导致同一个业务的类散落在各处,跟踪起来会显的很乱。纯粹按业务就会各种不同分工的类都在一个包里,同一个包就像一个大杂烩,很难统一管理和复用。通常情况下,我们是两者结合使用,业务分在同一个包中,工具类都聚合在一起。

下面是exception包的类图:

image.png

可以看到IbatisException是顶级的父类,但是已经被标记废弃了,为什么?因为它除了继承RuntimeException什么也没干,也没有业务异常来直接继承它,所以不再使用了。PersistenceException虽在这图里是二当家,但掌握实权,可以说是mybatis中绝大多数类的父类了。TooManyResultsException只用在一个地方,但也出现在exception包中我也比较诧异。另外一个ExceptionFactory相对独立,但也是给PersistenceException打工的,他的作用就是生成PersistenceException的工厂,不过是将业务的异常信息,异常栈传递的PersistenceException的构造方法。

下面是全部的mybatis异常了:

image.png
最终都是以PersistenceException的形式展现出来,其余都是根据内部业务定义的各类异常,是不是看起来也没什么复杂的。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

go-zero实战:让微服务Go起来——7 支付服务(pay

发表于 2021-11-29
  • 进入服务工作区
1
bash复制代码$ cd mall/service/pay

7.1 生成 pay model 模型

  • 创建 sql 文件
1
bash复制代码$ vim model/pay.sql
  • 编写 sql 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
sql复制代码CREATE TABLE `pay` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
`oid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '订单ID',
`amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品金额',
`source` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付方式',
`status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付状态',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_uid` (`uid`),
KEY `idx_oid` (`oid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  • 运行模板生成命令
1
bash复制代码$ goctl model mysql ddl -src ./model/pay.sql -dir ./model -c

7.2 生成 pay api 服务

  • 创建 api 文件
1
bash复制代码$ vim api/pay.api
  • 编写 api 文件
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
go复制代码type (
// 支付创建
CreateRequest {
Uid int64 `json:"uid"`
Oid int64 `json:"oid"`
Amount int64 `json:"amount"`
}
CreateResponse {
Id int64 `json:"id"`
}
// 支付创建

// 支付详情
DetailRequest {
Id int64 `json:"id"`
}
DetailResponse {
Id int64 `json:"id"`
Uid int64 `json:"uid"`
Oid int64 `json:"oid"`
Amount int64 `json:"amount"`
Source int64 `json:"source"`
Status int64 `json:"status"`
}
// 支付详情

// 支付回调
CallbackRequest {
Id int64 `json:"id"`
Uid int64 `json:"uid"`
Oid int64 `json:"oid"`
Amount int64 `json:"amount"`
Source int64 `json:"source"`
Status int64 `json:"status"`
}
CallbackResponse {
}
// 支付回调

)

@server(
jwt: Auth
)
service Pay {
@handler Create
post /api/pay/create(CreateRequest) returns (CreateResponse)

@handler Detail
post /api/pay/detail(DetailRequest) returns (DetailResponse)

@handler Callback
post /api/pay/callback(CallbackRequest) returns (CallbackResponse)
}
  • 运行模板生成命令
1
bash复制代码$ goctl api go -api ./api/pay.api -dir ./api

7.3 生成 pay rpc 服务

  • 创建 proto 文件
1
bash复制代码$ vim rpc/pay.proto
  • 编写 proto 文件
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
protobuf复制代码syntax = "proto3";

package pay;

option go_package = "./pay";

// 支付创建
message CreateRequest {
int64 Uid = 1;
int64 Oid = 2;
int64 Amount = 3;
}
message CreateResponse {
int64 id = 1;
}
// 支付创建

// 支付详情
message DetailRequest {
int64 id = 1;
}
message DetailResponse {
int64 id = 1;
int64 Uid = 2;
int64 Oid = 3;
int64 Amount = 4;
int64 Source = 5;
int64 Status = 6;
}
// 支付详情

// 支付详情
message CallbackRequest {
int64 id = 1;
int64 Uid = 2;
int64 Oid = 3;
int64 Amount = 4;
int64 Source = 5;
int64 Status = 6;
}
message CallbackResponse {
}
// 支付详情


service Pay {
rpc Create(CreateRequest) returns(CreateResponse);
rpc Detail(DetailRequest) returns(DetailResponse);
rpc Callback(CallbackRequest) returns(CallbackResponse);
}
  • 运行模板生成命令
1
bash复制代码$ goctl rpc protoc ./rpc/pay.proto --go_out=./rpc/types --go-grpc_out=./rpc/types --zrpc_out=./rpc

7.4 编写 pay rpc 服务

7.4.1 修改配置文件

  • 修改 pay.yaml 配置文件
1
bash复制代码$ vim rpc/etc/pay.yaml
  • 修改服务监听地址,端口号为0.0.0.0:9003,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yaml复制代码Name: pay.rpc
ListenOn: 0.0.0.0:9003

Etcd:
Hosts:
- etcd:2379
Key: pay.rpc

Mysql:
DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

CacheRedis:
- Host: redis:6379
Type: node
Pass:

7.4.2 添加 pay model 依赖

  • 添加 Mysql 服务配置,CacheRedis 服务配置的实例化
1
bash复制代码$ vim rpc/internal/config/config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go复制代码package config

import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
zrpc.RpcServerConf

Mysql struct {
DataSource string
}

CacheRedis cache.CacheConf
}
  • 注册服务上下文 pay model 的依赖
1
bash复制代码$ vim rpc/internal/svc/servicecontext.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
go复制代码package svc

import (
"mall/service/pay/model"
"mall/service/pay/rpc/internal/config"

"github.com/zeromicro/go-zero/core/stores/sqlx"
)

type ServiceContext struct {
Config config.Config

PayModel model.PayModel
}

func NewServiceContext(c config.Config) *ServiceContext {
conn := sqlx.NewMysql(c.Mysql.DataSource)
return &ServiceContext{
Config: c,
PayModel: model.NewPayModel(conn, c.CacheRedis),
}
}

7.4.3 添加 user rpc,order rpc 依赖

  • 添加 user rpc, order rpc 服务配置
1
bash复制代码$ vim rpc/etc/pay.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
yaml复制代码Name: pay.rpc
ListenOn: 0.0.0.0:9003
Etcd:
Hosts:
- etcd:2379
Key: pay.rpc

......

UserRpc:
Etcd:
Hosts:
- etcd:2379
Key: user.rpc

OrderRpc:
Etcd:
Hosts:
- etcd:2379
Key: order.rpc
  • 添加 user rpc, order rpc 服务配置的实例化
1
bash复制代码$ vim rpc/internal/config/config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
go复制代码package config

import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
zrpc.RpcServerConf

Mysql struct {
DataSource string
}

CacheRedis cache.CacheConf

UserRpc zrpc.RpcClientConf
OrderRpc zrpc.RpcClientConf
}
  • 注册服务上下文 user rpc, order rpc 的依赖
1
go复制代码$ vim rpc/internal/svc/servicecontext.go
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
go复制代码package svc

import (
"mall/service/order/rpc/order"
"mall/service/pay/model"
"mall/service/pay/rpc/internal/config"
"mall/service/user/rpc/user"

"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/zrpc"
)

type ServiceContext struct {
Config config.Config

PayModel model.PayModel

UserRpc user.User
OrderRpc order.Order
}

func NewServiceContext(c config.Config) *ServiceContext {
conn := sqlx.NewMysql(c.Mysql.DataSource)
return &ServiceContext{
Config: c,
PayModel: model.NewPayModel(conn, c.CacheRedis),
UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),
OrderRpc: order.NewOrder(zrpc.MustNewClient(c.OrderRpc)),
}
}

7.4.4 添加支付创建逻辑 Create

  • 添加根据 oid 查询订单支付记录 PayModel 方法 FindOneByOid
1
bash复制代码$ vim model/paymodel.go
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
go复制代码package model

......

var (
cachePayOidPrefix = "cache:pay:oid:"
)

type (
// PayModel is an interface to be customized, add more methods here,
// and implement the added methods in customPayModel.
PayModel interface {
payModel

FindOneByOid(ctx context.Context, oid int64) (*Pay, error)
}

customPayModel struct {
*defaultPayModel
}
)

......

func (m *defaultPayModel) FindOneByOid(ctx context.Context, oid int64) (*Pay, error) {
payOidKey := fmt.Sprintf("%s%v", cachePayOidPrefix, oid)
var resp Pay
err := m.QueryRowCtx(ctx, &resp, payOidKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
query := fmt.Sprintf("select %s from %s where `oid` = ? limit 1", payRows, m.table)
return conn.QueryRowCtx(ctx, v, query, oid)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}

......
  • 添加支付创建逻辑
1
bash复制代码$ vim rpc/internal/logic/createlogic.go
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
go复制代码package logic

import (
"context"

"mall/service/order/rpc/types/order"
"mall/service/pay/model"
"mall/service/pay/rpc/internal/svc"
"mall/service/pay/rpc/types/pay"
"mall/service/user/rpc/types/user"

"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/status"
)

type CreateLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}

func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
return &CreateLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}

func (l *CreateLogic) Create(in *pay.CreateRequest) (*pay.CreateResponse, error) {
// 查询用户是否存在
_, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{
Id: in.Uid,
})
if err != nil {
return nil, err
}

// 查询订单是否存在
_, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{
Id: in.Oid,
})
if err != nil {
return nil, err
}

// 查询订单是否已经创建支付
_, err = l.svcCtx.PayModel.FindOneByOid(l.ctx, in.Oid)
if err == nil {
return nil, status.Error(100, "订单已创建支付")
}

newPay := model.Pay{
Uid: in.Uid,
Oid: in.Oid,
Amount: in.Amount,
Source: 0,
Status: 0,
}

res, err := l.svcCtx.PayModel.Insert(l.ctx, &newPay)
if err != nil {
return nil, status.Error(500, err.Error())
}

newPay.Id, err = res.LastInsertId()
if err != nil {
return nil, status.Error(500, err.Error())
}

return &pay.CreateResponse{
Id: newPay.Id,
}, nil
}

7.4.5 添加支付详情逻辑 Detail

1
bash复制代码$ vim rpc/internal/logic/detaillogic.go
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
go复制代码package logic

import (
"context"

"mall/service/pay/model"
"mall/service/pay/rpc/internal/svc"
"mall/service/pay/rpc/types/pay"

"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/status"
)

type DetailLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}

func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic {
return &DetailLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}

func (l *DetailLogic) Detail(in *pay.DetailRequest) (*pay.DetailResponse, error) {
// 查询支付是否存在
res, err := l.svcCtx.PayModel.FindOne(l.ctx, in.Id)
if err != nil {
if err == model.ErrNotFound {
return nil, status.Error(100, "支付不存在")
}
return nil, status.Error(500, err.Error())
}

return &pay.DetailResponse{
Id: res.Id,
Uid: res.Uid,
Oid: res.Oid,
Amount: res.Amount,
Source: res.Source,
Status: res.Status,
}, nil
}

7.4.6 添加支付回调逻辑 Callback

1
bash复制代码$ vim rpc/internal/logic/callbacklogic.go
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
go复制代码package logic

import (
"context"

"mall/service/order/rpc/types/order"
"mall/service/pay/model"
"mall/service/pay/rpc/internal/svc"
"mall/service/pay/rpc/types/pay"
"mall/service/user/rpc/types/user"

"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/status"
)

type CallbackLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}

func NewCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CallbackLogic {
return &CallbackLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}

func (l *CallbackLogic) Callback(in *pay.CallbackRequest) (*pay.CallbackResponse, error) {
// 查询用户是否存在
_, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{
Id: in.Uid,
})
if err != nil {
return nil, err
}

// 查询订单是否存在
_, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{
Id: in.Oid,
})
if err != nil {
return nil, err
}

// 查询支付是否存在
res, err := l.svcCtx.PayModel.FindOne(l.ctx, in.Id)
if err != nil {
if err == model.ErrNotFound {
return nil, status.Error(100, "支付不存在")
}
return nil, status.Error(500, err.Error())
}
// 支付金额与订单金额不符
if in.Amount != res.Amount {
return nil, status.Error(100, "支付金额与订单金额不符")
}

res.Source = in.Source
res.Status = in.Status

err = l.svcCtx.PayModel.Update(l.ctx, res)
if err != nil {
return nil, status.Error(500, err.Error())
}

// 更新订单支付状态
_, err = l.svcCtx.OrderRpc.Paid(l.ctx, &order.PaidRequest{
Id: in.Oid,
})
if err != nil {
return nil, status.Error(500, err.Error())
}

return &pay.CallbackResponse{}, nil
}

7.5 编写 pay api 服务

7.5.1 修改配置文件

  • 修改 pay.yaml 配置文件
1
bash复制代码$ vim api/etc/pay.yaml
  • 修改服务地址,端口号为0.0.0.0:8003,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yaml复制代码Name: Pay
Host: 0.0.0.0
Port: 8003

Mysql:
DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

CacheRedis:
- Host: redis:6379
Type: node
Pass:

Auth:
AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl
AccessExpire: 86400

7.5.2 添加 pay rpc 依赖

  • 添加 pay rpc 服务配置
1
bash复制代码$ vim api/etc/pay.yaml
1
2
3
4
5
6
7
8
9
10
11
yaml复制代码Name: Pay
Host: 0.0.0.0
Port: 8003

......

PayRpc:
Etcd:
Hosts:
- etcd:2379
Key: pay.rpc
  • 添加 pay rpc 服务配置的实例化
1
bash复制代码$ vim api/internal/config/config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go复制代码package config

import (
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
rest.RestConf

Auth struct {
AccessSecret string
AccessExpire int64
}

PayRpc zrpc.RpcClientConf
}
  • 注册服务上下文 pay rpc 的依赖
1
bash复制代码$ vim api/internal/svc/servicecontext.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
go复制代码package svc

import (
"mall/service/pay/api/internal/config"
"mall/service/pay/rpc/pay"

"github.com/zeromicro/go-zero/zrpc"
)

type ServiceContext struct {
Config config.Config

PayRpc pay.Pay
}

func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
PayRpc: pay.NewPay(zrpc.MustNewClient(c.PayRpc)),
}
}

7.5.3 添加支付创建逻辑 Create

1
bash复制代码$ vim api/internal/logic/createlogic.go
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
go复制代码package logic

import (
"context"

"mall/service/pay/api/internal/svc"
"mall/service/pay/api/internal/types"
"mall/service/pay/rpc/types/pay"

"github.com/zeromicro/go-zero/core/logx"
)

type CreateLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}

func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
return &CreateLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}

func (l *CreateLogic) Create(req *types.CreateRequest) (resp *types.CreateResponse, err error) {
res, err := l.svcCtx.PayRpc.Create(l.ctx, &pay.CreateRequest{
Uid: req.Uid,
Oid: req.Oid,
Amount: req.Amount,
})
if err != nil {
return nil, err
}

return &types.CreateResponse{
Id: res.Id,
}, nil
}

7.5.4 添加支付详情逻辑 Detail

1
bash复制代码$ vim api/internal/logic/detaillogic.go
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
go复制代码package logic

import (
"context"

"mall/service/pay/api/internal/svc"
"mall/service/pay/api/internal/types"
"mall/service/pay/rpc/types/pay"

"github.com/zeromicro/go-zero/core/logx"
)

type DetailLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}

func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic {
return &DetailLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}

func (l *DetailLogic) Detail(req *types.DetailRequest) (resp *types.DetailResponse, err error) {
res, err := l.svcCtx.PayRpc.Detail(l.ctx, &pay.DetailRequest{
Id: req.Id,
})
if err != nil {
return nil, err
}

return &types.DetailResponse{
Id: req.Id,
Uid: res.Uid,
Oid: res.Oid,
Amount: res.Amount,
Source: res.Source,
Status: res.Status,
}, nil
}

7.5.5 添加支付回调逻辑 Callback

1
bash复制代码$ vim api/internal/logic/callbacklogic.go
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
go复制代码package logic

import (
"context"

"mall/service/pay/api/internal/svc"
"mall/service/pay/api/internal/types"
"mall/service/pay/rpc/types/pay"

"github.com/zeromicro/go-zero/core/logx"
)

type CallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}

func NewCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CallbackLogic {
return &CallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}

func (l *CallbackLogic) Callback(req *types.CallbackRequest) (resp *types.CallbackResponse, err error) {
_, err = l.svcCtx.PayRpc.Callback(l.ctx, &pay.CallbackRequest{
Id: req.Id,
Uid: req.Uid,
Oid: req.Oid,
Amount: req.Amount,
Source: req.Source,
Status: req.Status,
})
if err != nil {
return nil, err
}

return &types.CallbackResponse{}, nil
}

7.6 启动 pay rpc 服务

!提示:启动服务需要在 golang 容器中启动

1
2
3
bash复制代码$ cd mall/service/pay/rpc
$ go run pay.go -f etc/pay.yaml
Starting rpc server at 127.0.0.1:9003...

7.7 启动 pay api 服务

!提示:启动服务需要在 golang 容器中启动

1
2
3
bash复制代码$ cd mall/service/pay/api
$ go run pay.go -f etc/pay.yaml
Starting server at 0.0.0.0:8003...

项目地址:github

上一篇《go-zero实战:让微服务Go起来——6 订单服务(order)》

下一篇《go-zero实战:让微服务Go起来——8 RPC服务 Auth 验证》

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…113114115…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%