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

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


  • 首页

  • 归档

  • 搜索

【计算机图形学】圆与椭圆的绘制——MATLAB实现

发表于 2021-11-25

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

1 引言

圆与椭圆的绘制,其核心算法也是Bresenham中点算法,只是分界点的条件不同。

由于圆与椭圆的对称性也有所区别,如圆是八分之一对称,而椭圆是四分之一对称,故绘制方法也有所差异。

2 思路

2.1 圆

假设从(0,R)开始推算递归公式,当x=y时,即X方向的法向量与y方向的法向量相等。

故只需绘制八分之一圆弧,再根据对称性完成整个圆的绘制。

算法大致步骤如下:

  1. 首先将圆心在原点的圆标准方程化为隐式形式,得到:F(x,y)=x2+y2−R2F(x,y)=x^2+y^2-R^2F(x,y)=x2+y2−R2
  2. 递归判断两个待选点中点与圆弧的位置关系,进而在两个待选点中选择其一,再继续向前判断
  3. 表达式的两种情况:
* d≤0,d=d+2x+3d\leq0,d=d+2x+3d≤0,d=d+2x+3
* d>0,d=d+2(x−y)+5d>0,d=d+2(x-y)+5d>0,d=d+2(x−y)+5
  1. 递归构造表达式,循环保存点坐标,最后一次绘制

2.2 椭圆

关于椭圆,只需绘制第一象限的椭圆弧,再根据其对称性完成整个椭圆的绘制

算法大致步骤如下:

  1. 首先将中心点在圆心的椭圆标准方程化为隐式形式,得到:F(x,y)=b2x2+a2y2−a2b2F(x,y)=b^2x^2+a^2y^2-a^2b^2F(x,y)=b2x2+a2y2−a2b2
  2. 再找到X方向的法向量与y方向的法向量相等的点作为分界点,以此分为椭圆弧的上半部分和下半部分
  3. 分别对上半部分和下半部分构造不同的初始判别表达式
* d=b2−a2+0.25a2d=b^2-a^2+0.25a^2d=b2−a2+0.25a2
* d=b2(x+0.5)2+a2(y−1)2−a2b2d=b^2(x+0.5)^2+a^2(y-1)^2-a^2b^2d=b2(x+0.5)2+a2(y−1)2−a2b2
  1. 递归构造表达式,循环保存点坐标,最后一次绘制

3 过程

3.1 圆

首先,为判断表达式赋初始值,即d=1.25-R;

再循环判断,并保存点坐标

1
2
3
4
5
6
7
8
9
10
11
matlab复制代码while x<=(2^0.5/2)*R
if d<0
d=d+2*(x(end))+3;
x(end+1)=x(end)+1;
y(end+1)=y(end);
else
d=d+2*(x(end)-y(end))+5;
x(end+1)=x(end)+1;
y(end+1)=y(end)-1;
end
end

如需导出坐标矩阵,还可分段保存点坐标;最后一次绘制即可完成。

3.2 椭圆

首先,在第一象限椭圆弧上半部分,构造判断表达式,并循环为x,y坐标矩阵赋值

1
2
3
4
5
6
7
8
9
10
11
12
matlab复制代码d=b*b-a*a+0.25*a*a;
while(b*b*x(end)<a*a*y(end))
if(d<=0)
d=d+b*b*(2*x(end)+3);
x(end+1)=x(end)+1;
y(end+1)=y(end);
else
d=d+b*b*(2*x(end)+3)+2*a*a*(1-y(end));
x(end+1)=x(end)+1;
y(end+1)=y(end)-1;
end
end

同理,在下半部分,构造相应的表达式如下:

1
matlab复制代码d=b*b*(x(end)+0.5)*(x(end)+0.5)+a*a*(y(end)-1)*(y(end)-1)-a*a*b*b;

如需转换为中心点在任意位置的情况,需将原点平移,调用时再将平移参数作为函数的入口参数传入

1
2
3
matlab复制代码%平移坐标原点
x=x+m;
y=y+n;

如需导出坐标矩阵,还可分段保存点坐标

1
2
3
matlab复制代码%存储椭圆上点坐标
xx=[x,fliplr(x),2*m-x,fliplr(2*m-x)];
yy=[y,fliplr(2*n-y),2*n-y,fliplr(y)];

最后只需一次绘制,即可完成

1
matlab复制代码plot(xx,yy,'y-');

4 结果

同时绘制圆和椭圆再叠加,结果如下:

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

本文转载自: 掘金

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

Go语言 基于gin定义一个简单的web server 开发

发表于 2021-11-25

之前的问题

image.png

之前的代码问题就在于 不支持命令行参数,这会导致 程序的运行比较死板。比如常见的是

我们的可执行文件和 对应的配置文件不在一个目录下的时候 就会出现读不到配置文件的问题

又或者是上述的场景, 虽然 我们的可执行文件和配置文件都在一个目录下

但是我们执行命令的地方 下面并没有配置文件 那也是无法执行的。

所以这套机制 很不灵活。

读取命令行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码package main

import (
"fmt"
"os"
)

func main() {
if len(os.Args) > 0 {
for index, arg := range os.Args {
fmt.Printf("args[%d]=%v\n", index, arg)
}
}
}

看下程序的执行结果:

image.png

很好分辨,其实os的args 就是一个字符串的数组,第一个位置是可执行文件自己,第二个位置开始就是参数了

有了这个基础 再去修改我们的 代码就很容易了

修改框架

首先我们这个初始化文件的地方 增加一个filePath的参数

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
go复制代码// Init 加载配置文件
func Init(filePath string) error {
// 如果设置filepath就直接使用 否则用当前目录的
if len(filePath) == 0 {
viper.SetConfigName("config") // 配置文件的名称
viper.SetConfigType("yaml") // 配置文件的扩展名,这里除了json还可以有yaml等格式
// 这个配置可以有多个,主要是告诉viper 去哪个地方找配置文件
// 我们这里就是简单配置下 在当前工作目录下 找配置即可
viper.AddConfigPath(".")
} else {
viper.SetConfigFile(filePath)
}

err := viper.ReadInConfig()
if err != nil {
fmt.Println("viper init failed:", err)
return err
}
// 变化就在这里 有个序列化对象的过程
if err := viper.Unmarshal(Config); err != nil {
fmt.Println("viper Unmarshal err", err)
}
viper.WatchConfig()
viper.OnConfigChange(func(in fsnotify.Event) {
fmt.Println("配置文件已修改")
})
return err
}

然后在main函数的开头 读取一下参数即可

1
2
3
4
5
6
7
8
9
10
11
go复制代码func main() {

filePath := ""
if len(os.Args) >= 2 {
filePath = os.Args[1]
}
// 加载配置文件
if err := setting.Init(filePath); err != nil {
fmt.Printf("init settings failed:%s \n", err)
return
}

更加标准的支持命令行参数

上述的写法虽然解决了痛点,但是好像不太标准,大部分的时候我们使用别人的程序 都是 -t -p 等等,这里我们也要专业一点 推荐使用flag这个包 来解决我们的问题,代码很简单。

看下main函数的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码filePath := ""
// 第二个参数 就是命令行的-x 第三个是默认值 第四个是-h的时候的说明
flag.StringVar(&filePath, "file", "", "配置文件地址")
flag.Parse()
// 加载配置文件
if err := setting.Init(filePath); err != nil {
fmt.Printf("init settings failed:%s \n", err)
return
}
// 初始化日志
if err := logger.Init(setting.Config.LogConfig); err != nil {
fmt.Printf("init settings failed:%s \n", err)
return
}

然后看下实际的效果:

image.png

至此 一个功能基本完备的 web server 开发框架 就结束了。

源码地址

本文转载自: 掘金

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

深入Java单元测试mock技术Mockito的原理 Moc

发表于 2021-11-25

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

在软件开发的单元测试环节,外部调用和第三方代码没必要测试,因为可能无法测试,也可能会影响测试效率。为了不测试恼人的外部调用和第三方代码,我们经常需要模拟这些方法的返回值,这就是测试常用的mock技术。JAVA测试框架Mockito是这样的一个测试框架,本文将深入浅出Mockito的工作原理。

Mockito

Mockito使用不难,操作方便。但是问起具体的工作机制来,却不甚清楚,需要好好整理一番。

基本使用

使用上按照invoke-when-then-invoke这样的步骤去使用就可以了,即插桩前调用-插桩-插桩后调用。

如下案例所示,这里用的是3.9.0版本的mockito-core包:

1
2
3
4
5
6
xml复制代码<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
1
2
3
4
java复制代码A a = Mockito.mock(A.class);
System.out.println("a.test() = " + a.test());
Mockito.when(a.test()).thenReturn(new MyMap());
System.out.println("a.test() = " + a.test());
  • 返回符合预期
1
2
css复制代码a.test() = null
a.test() = {}
  • 插桩指的是when-then的过程,指定方法参数,模拟结果返回。

Mock到底做了什么?

使用看起来很简单,那么,Mock到底做了什么?怎么就修改了类的既定逻辑?

  • 其实并没有修改原类的既定逻辑,而是调用mock工具函数会生成一个Mock对象
  • 这个对象是继承了原类的一个子类
    如下示意图:

mock3.png

  • 子类是用字节码框架在运行时生成的
    • 使用Byte Buddy字节码框架处理技术生成的字节码
    • 字节码可以直接加载,选定一个合适的类加载器加载生成的字节码,并实例化为一个对象

为什么要生成字节码呢,因为如果是生成源码还要先编译再加载,反而多了一个步骤。

从图上我们看到,mock对象处理的方法都交给mockHandler实例的handle方法去处理,下面将详细分析这个对象和方法做了什么。

关系图

除了生成字节码,mock同时还做了一些很重要的事情,如下图生成了一些实例对象:

mock4.png
这些实例构成了mock和stub过程中的处理器和容器:

  • MockHandler
  • invocationContainer
  • stubbed

mockHandler等对象

上面的图示过程可以在代码里找到印证,最主要的执行逻辑是在createMock方法里面

1
2
3
4
5
6
7
8
9
10
java复制代码    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
//生成并加载mock类
Class<? extends T> mockedProxyType = createMockType(settings);
//实例化
T mockInstance = instantiator.newInstance(mockedProxyType);
MockAccess mockAccess = (MockAccess) mockInstance;
//给对象成员变量mockitoInterceptor赋值<-handler
mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));
return ensureMockIsAssignableToMockedType(settings, mockInstance);
}
  • 在类MockUtil创建的handler: MockHandler mockHandler = createMockHandler(settings)
  • 在这个创建handler方法的里面生成invocationContainer: this.invocationContainer = new InvocationContainerImpl(mockSettings)
  • invocationContainer里面创建stubbed LinkedList<StubbedInvocationMatcher>用来存放打桩的函数和参数以及返回的结果。
  • 给对象成员变量mockitoInterceptor赋值为handler

这里,已经先通过Bytebuddy生成的字节码声明了mockitoInterceptor成员,这样再通过mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings))就给它赋值了。

1
2
3
4
5
6
java复制代码 Bytebuddy.builder()...//设置父类、名字
.method(matcher)
.intercept(dispatcher)
...//设置其他方法属性,比如synchronized设置
//声明成员变量: mockitoInterceptor
.defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)

这样之后,mock对象里面的成员mockHander,通过getMockitoInterceptor函数能获取到,这个非常重要。

关系图增强

另外,MockingProgress是stub过程的工具类,在mock的过程中也生成了MockingProgress类的实例。可以看到:

1
2
3
4
5
6
Java复制代码public <T> T mock(Class<T> typeToMock, MockSettings settings) {
...
T mock = createMock(creationSettings);
mockingProgress().mockingStarted(mock, creationSettings);
return mock;
}
  • mockingProgress()是生成一个ThreadLocal<MockingProgress>

于是,现有的关系图增强了:

mock6.png
下面会具体分析MockingProgress的作用。

执行流程

假设线程thread1开始执行mock,执行流程如下图所示:

mock11.png

  • A a=Mockito.mock(A.class)初始化…
  • Mockito.when(a.func(...)).then...,这里可以分解为如下三个流程

调用

这是when里面的那一次调用:

  • a.func(“abc”) 执行了handle方法
  • handle方法里生成局部ongoingStubbing对象
  • handler传递自己的成员invocationContainer给ongoingStubbing
  • handler将生成的ongoingStubbing对象push 到mockingProgress
  • 此时返回默认的empty结果null
    如下代码所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码    public Object handle(Invocation invocation) throws Throwable {
OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainer);
mockingProgress().reportOngoingStubbing(ongoingStubbing);
StubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);
if (stubbing != null) {
stubbing.captureArgumentsFrom(invocation);
return stubbing.answer(invocation);
} else {
//使用的Mockito最初的withSettings()提供的默认返回值,null,
//when里面的那一次调用就是返回的默认值
return mockSettings.getDefaultAnswer().answer(invocation);
}
}

when

when逻辑的执行:从当前线程的mockingProgress拉取pull对象ongoingStubbing

  • mockingProgress
    • 如果stubbingInProgress!=null,则前面的stub还没完成,将抛出异常
    • 如果stubbingInProgress=null,则设置stubbingInProgress,返回ongoingStubbing给when,设置自己的ongoingStubbing为null,when函数返回ongoingStubbing
1
2
3
4
java复制代码 public <T> OngoingStubbing<T> when(T methodCall) {
mockingProgress().stubbingStarted();//如果stubbingInProgress!=null,则前面的stub还没完成
return (OngoingStubbing<T>) mockingProgress.pullOngoingStubbing();
}

then

  • when函数返回的ongoingStubbing调用了then方法。
  • 然后then从ongoingStubbing的成员invocationContainer拿到stubbed
  • 加锁并修改这个链表,也就是加入一个打桩的函数和参数以及返回的结果。
    通过invocationContainer可以知道,invoke送来的方法参数和取走后设置的返回值类型要对应,不然then执行会报错

再调用

最后使用stub的结果

  • a.func("abc"):从invocationContainer的stubbed匹配调用参数,能匹配则返回调用的结果

执行流程的总结

通过上面的分析可以看出,使用ThreadLocal的mockingProgress使得mock的过程在一个线程里面变得可行。

MockingProgress引导状态演化

对于 MockingProgress来说

  • invoke 送来ongoingStubbing
    • 可以送来多次,最后一次的为准
  • when 取走ongoingStubbing
    • 不能取走多次
      在执行各个操作的时候会判断ongoingStubbing是否为null

mock8.png

  • when 需要ongoingStubbing有值,之后then是ongoingStubbing调用的
  • UnfinishedStubbingException
+ stub过程 when-then是原子的,而且stub没有完成时调用a的方法或继续发起新的stub都会抛出`Method threw 'org.mockito.exceptions.misusing.UnfinishedStubbingException' exception.`

MockingProgress再探究

小实验:多线程mock共享

一个线程mock的数据,另外一个线程是可以使用的。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Test
void multiMockA() throws Exception{
A a = Mockito.mock(A.class);
Mockito.when(a.func("one")).thenReturn(1);
Thread thread = new Thread(() -> {
Mockito.when(a.func("two")).thenReturn(2);
System.out.println("a.func("one") = " + a.func("one"));
});
thread.start();
thread.join();
System.out.println("a.func("two") = " + a.func("two"));
}

返回

1
2
shell复制代码a.func("one") = 1
a.func("two") = 2

这个小实验的目的是希望碰到了可以理解,但也不提倡这种用法。

小实验:多线程mock异常

1
2
3
4
5
6
7
8
9
10
scss复制代码@Test
void multiMock() throws Exception {
Aclass a = Mockito.mock(Aclass.class);
new Thread(() -> {
Mockito.when(a.func2("two")).thenReturn("2");
}).start();
Mockito.when(a.func("one")).thenReturn(1);

Thread.sleep(100);
}

执行发现抛出异常String cannot be returned by func()

这是什么原因呢?

debug发现两个线程的函数执行顺序如下:

mock1.png

  • thread1和thread2共享a的invocationContainer对象
  • invocationContainer对象的invocationForStubbing成员
    • 方法调用的时候通过invocationContainer.setMethodForStubbing(invocationMatcher) 设置的invocationForStubbing
    • invocationForStubbing成员先被设置为a.func(“one”),后被重置为a.func2(“two”)
      因为Invocation被替换了,所以当thread1再调用then的时候会抛出类型不匹配的异常。

小实验:一个线程里mock多个类

如下代码:

1
2
3
4
5
6
7
8
9
Java复制代码@Test
void mocMultiClass() throws Exception{
A a = Mockito.mock(A.class);
B b= Mockito.mock(B.class);
Mockito.when(a.func("one")).thenReturn(1);
Mockito.when(b.func(1)).thenReturn("one");
System.out.println("a.func("one") = " + a.func("one"));
System.out.println("b.func(1) = " + b.func(1));
}
1
2
go复制代码a.func("one") = 1
b.func(1) = one

这个正常操作都没有问题。
可以看出,MockingProgress帮着A stub 完,又帮着B mock,如下示意图:

mock9.png

不常用但可以了解的功能

最常用的就是上面详细描述的这种模式了,但也有一些不常用但可以了解的功能,主要是下面两种。

SPY模式

Spy是另外一种插桩的方式,和mock相比,字节码生成的时候interceptor处理的逻辑不同。

  • spy生成的对象,stub之前的方法调用(invoke),是调用原来的类(super)的方法处理逻辑。
  • mock生成的对象,stub之前的方法调用是返回空值,比如null,集合类则是空集合。
    • Mockito支持了几种集合类的空值返回
    • 对于HashMap这样的返回类型,Mockito是返回一个空的集合
    • 但是如果返回的是继承HashMap的类,比如NutMap,Mockito还是返回null

VERIFY模式

  • 校验MOCK的方法是否调用过
  • 校验MOCK的方法调用次数等信息

经验总结

总结了一些生产过程里遇到的几个案例分享,包括在SpringBoot里面使用Mock、mock带泛型的对象时遇到的问题以及插桩的参数的一些问题。

SpringBoot和Mock

在Spingboot的测试里面,测试的场景更为复杂,我们可以使用:

  • @MockBean 用于注入mock的单例对象,@MockBean 是spring-boot-test包里用于帮助spring bean mock的注解。
    可以这么使用
1
2
Java复制代码@MockBean
KafkaTemplate kafkaTemplate;

在做单元测试时,如果想要 mock bean 的逻辑,只需要声明一个变量并在上面加上 @MockBean 的注释即可,
之后就和上面详述的意义。使用 when-then 来设定 mock 的行为。

在运行时,SpringBoot 会扫描到你注解的 @MockBean ,并自动装配到被测试的 Component 里。

  • 同理,@SpyBean 用于注入spy的单例对象,是spring-boot-test里用于帮助spring bean spy的注解。
    但是注意的一点:

当有多个测试类时,@MockBean注解会因不同的上下文从而导致springboot多次启动。

mock和泛型的关系

泛型只是一种Java编译时用到的语法糖🍬,比如下面这个例子:

1
2
3
4
5
6
java复制代码@Test
void mockGeneric(){
HashMap<String,Integer> list = Mockito.mock(HashMap.class);
Mockito.when(list.get("one")).thenReturn(1);
System.out.println("list.get("one") = " + list.get("one"));
}
1
csharp复制代码list.get("one") = 1

也就是说,mock和泛型是没有关系的。

但是当使用@MockBean的时候,我遇到了一个泛型相关的问题。苦恼了一阵子。

最开始的时候没注意,使用了方式1来做mock:

1
2
java复制代码@MockBean
KafkaTemplate kafkaTemplate;

结果发现mock根本就没有生效。

后面改为
方式2来能正确的mock:

1
2
java复制代码@MockBean
KafkaTemplate<String, Object> kafkaTemplate;

因为在Springboot的 Component里自动注入的是KafkaTemplate<String, Object> kafkaTemplate类型的变量,导致mock的KafkaTemplate没有被自动装配到被测试的 Component 里面,
这是因为Spring自动注入Bean也已经支持到泛型了。

Spring4的新特性就是把泛型的具体类型也作为类的一种分类方法(Qualifier)。这样我们的KafkaTemplate<String, Object> 和KafkaTemplate<String, Integer> 虽然是同一个类和KafkaTemplate,但是因为泛型的具体类型不同,也会被区分开。

也就是说,如果Component里使用的地方用的是泛型,则@MockBean声明的时候必须也要加上对应的泛型。

插桩的参数模式问题

  • 参数包括两种模式:固定的参数和匹配器
  • 匹配器:可以使用Mockito.any的一系列函数来匹配参数,包括ArgumentMatchers类下面提供的所有静态方法,eq,startsWith等等。
  • 方法的多个参数使用模式要一致,否则会抛出InvalidUseOfMatchersException(参数匹配)异常,也就是如果有一个参数是采用匹配模式,另外一个不能使用固定模式(非ArgumentMatchers提供的)。

本文转载自: 掘金

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

Linux 中的 15 个强大的 firewall-cmd

发表于 2021-11-25

企业中,因为业务的重要性,通常会对网络安全十分重视,那么一个好的防火墙系统就是强有力的利器!

本文,将带大家了解 CentOS 7新的防火墙服务firewalld的基本原理,它有个非常强大的过滤系统,称为 Netfilter,它内置于内核模块中,用于检查穿过系统的每个数据包。

这意味着它可以在到达目的地之前以编程方式检查、修改、拒绝或丢弃任何网络数据包,如传入、传出或转发,从 Centos-7 开始,firewalld 成为管理基于主机的防火墙服务的默认工具,firewalld 的守护进程是从 firewalld 包安装的,它将在操作系统的所有基本安装上可用,但在最小安装上不可用。

使用 FirewallD 优于“iptables”的优点:
  • 在运行时所做的任何配置更改都不需要重新加载或重新启动 firewalld 服务
  • 通过将整个网络流量安排到区域中来简化防火墙管理
  • 每个系统可以设置多个防火墙配置以更改网络环境
  • 使用 D-Bus 消息系统来交互/维护防火墙设置

在 CentOS 7 或更高版本中,我们仍然可以使用经典的 iptables,如果要使用 iptables,需要停止并禁用 firewalld 服务。同时使用firewalld 和 iptables会使系统混乱,因为它们彼此不兼容。

建议使用 firewalld 来管理防火墙服务,除非我们有一些特定的理由继续使用经典的 iptables。

Firewalld 设计了强大的过滤系统,并且在处理防火墙管理方面也更加灵活。为了利用这种设计,firewalld 将传入流量分类到源地址定义的接口上的区域中。

每个区域都旨在根据指定的标准管理流量。如果没有进行任何修改,默认区域将设置为 public,并且关联的网络接口将附加到 public。

所有预定义的区域规则都存储在两个位置:系统指定的区域规则在“/usr/lib/firewalld/zones/”下,用户指定的区域规则在/etc/firewalld/zones/ 下。如果在系统区域配置文件中进行了任何修改,它将自动复制到 /etc/firewalld/zones/。

本文将详细介绍firewalld 服务基础知识,了解如何在 RHEL/CentOS 7 中使用 firewall-cmd 命令。


本文目录如下:

  • 安装并启用firewallD服务
  • 区域
+ 1.如何查看firewalld中的所有可用区域?
+ 2. 如何找出哪个是默认区域?
+ 3. 如何查找活动区域和相关网络接口的列表?
+ 4. 如何查看活动公共区域是否有任何规则列出?
+ 5. 如何查看所有可用区域的列表?
+ 6. 如何将默认区域更改为特定区域?
+ 7. 如何将网络接口从一个区域更改为另一个区域?
+ 8. 如何建立自定义的firewalld zone?
  • 服务
+ 1.如何列出firewalld中所有可用的服务?
+ 2. 如何列出特定区域内的所有可用服务?
+ 3. 如何将现有服务添加到默认区域?
  • firewalld两种模式
+ 1. 如何永久添加服务?
+ 2. 如何将我的运行时设置迁移到永久设置?
  • 端口
+ 1. 如何在公共区域为samba服务开放端口?
  • 超时

前置条件:

  • 操作系统:CentOS 7 或更高版本
  • 软件包:firewalld
  • 用户帐户:root 用户或具有 sudo 权限的用户帐户
  • 建议使用 sudo 权限而不是 root 来运行所有管理命令

有三种方式配置防火墙:

  • 在“/etc/firewalld”配置文件中直接编辑
  • 图形界面“firewall-config”工具
  • 终端中的命令行“firewall-cmd”

注意:本文我们将只关注“firewall-cmd”命令。

安装并启用firewallD服务

首先,更新包的最新当前版本。

1
sql复制代码sudo yum update -y

Firewalld在 CentOS 7 的所有基本安装上可用,但在最小安装上不可用,在这种情况下,我们可以使用以下命令进行安装:

1
php复制代码$ sudo yum install firewalld -y

使用以下命令启动和启用服务

1
2
3
php复制代码$ sudo systemctl start firewalld.service

$ sudo systemctl 启用 firewalld.service

使用以下命令验证防火墙服务的状态:

1
css复制代码$ sudo firewall-cmd --state
1
2
3
arduino复制代码Output:

running
1
lua复制代码$ sudo systemctl status firewalld

详细输出:

1
2
3
4
5
6
7
8
bash复制代码firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled)
Active: active (running) since Sat 2020-04-18 22:39:56 IST; 2h 52min ago
Main PID: 759 (firewalld)
CGroup: /system.slice/firewalld.service
└─759 /usr/bin/python -Es /usr/sbin/firewalld --nofork --nopid

Apr 18 22:39:56 localhost.localdomain systemd[1]: Started firewalld - dynamic...

提示:有些行被省略,使用 -l 显示完整。

区域

Firewalld 为不同的目的引入了几个预定义的区域和服务,主要目的之一是更轻松地处理 firewalld 管理。

基于这些区域和服务,我们可以阻止任何形式的系统传入流量,除非它明确允许在区域中使用一些特殊规则。

1.如何查看firewalld中的所有可用区域?

1
arduino复制代码$ sudo firewall-cmd --get-zones

这是 firewalld 中的默认预定义区域

2. 如何找出哪个是默认区域?

1
arduino复制代码$ firewall-cmd --get-default-zone
1
2
3
vbnet复制代码Output:

public

根据上面的输出,可以看到公共区域被标记为默认区域,我们可以根据我们的要求更改它,我们将在接下来的例子中进一步讨论。

3. 如何查找活动区域和相关网络接口的列表?

1
arduino复制代码$ firewall-cmd --get-active-zones
1
2
3
4
vbnet复制代码Output:

public
interfaces: enp1s0

在上面的输出中,您可以看到公共区域处于活动状态并与“enp1s0”网络接口相关联,如果没有任何接口未指定给特定区域,它将自动附加到默认区域。

4. 如何查看活动公共区域是否有任何规则列出?

1
ini复制代码$ sudo firewall-cmd --list-all --zone="public"

上面的输出显示公共区域处于活动状态并设置为默认值,网络接口“enp1so”与活动区域相关联,在此区域中,dhcpv6-client 和 ssh 被允许通过防火墙服务。

5. 如何查看所有可用区域的列表?

1
css复制代码$ sudo firewall-cmd --list-all-zones

与前面的例子类似,这里也会分别列出每个可用区域的详细配置页面,请自行检查,因为输出列表会很长。

6. 如何将默认区域更改为特定区域?

在更改到新区域之前,让我们检查现有的可用区域。

1
arduino复制代码$ sudo firewall-cmd --get-default-zone
1
2
3
vbnet复制代码Output:

public

在输出中,可以看到公共区域设置为默认区域,现在让我们尝试将区域从公共更改为工作。

1
arduino复制代码$ sudo firewall-cmd --set-default-zone=work
1
2
3
makefile复制代码Output:

success

如您所见,上述命令的输出是成功的,让我们验证一下。

1
arduino复制代码$ sudo firewall-cmd --get-default-zone
1
2
3
makefile复制代码Output:

work      <==

7. 如何将网络接口从一个区域更改为另一个区域?

如果系统有两个网络接口,比如“enp1s0 和 enp1s1”,默认情况下,所有接口都将被分配到默认区域,通过使用以下命令可以将接口更改为另一个区域。

1
csharp复制代码$ sudo firewall-cmd --zone=internal --change-interface=enp1s1

可以使用以下命令进行验证:

1
arduino复制代码$ sudo firewall-cmd --get-active-zones

8. 如何建立自定义的firewalld zone?

我们知道,所有系统指定的配置文件都位于“/usr/lib/firewalld/zones”,用户指定的文件位于“/etc/firewalld/zones”。

使用以下命令创建自定义区域文件允许使用端口号 80 和 22 的 ssh 和 apache 服务。

确保新文件应以 .xml 格式保存在用户定义的位置,目前,名称区域文件的长度仅限于 17 个字符。

1
shell复制代码$ sudo vi /etc/firewalld/zones/ linuxtecksecure .xml
1
2
3
4
5
6
7
8
9
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>linuxtecksecure</short>
<description>用于企业领域。</description>
<service name="apache"/>
<service name="ssh"/>
<port protocol="tcp" port="80"/>
<port protocol="tcp" port="22"/>
</zone>

保存并退出。

重新加载防火墙服务:

1
css复制代码$ sudo firewall-cmd --reload
1
2
3
4
makefile复制代码Output:


success

现在,重新检查 firewalld 中的可用区域

1
arduino复制代码$ sudo firewall-cmd --get-zones
1
2
3
kotlin复制代码Output:

block dmz drop external home internal "linuxtecksecure" public trusted work

使用上面的命令,我们可以创建一个新的 (linuxtecksecure) zone 来默认仅启用 apache 和 ssh 服务,创建文件后,我们需要“重新加载”firewalld 服务,以便将区域激活到firewalld

请记住:在对现有区域文件进行任何更改/更新后,请务必重新加载您的firewalld 服务激活,否则防火墙中的更改不会受到影响。

服务

firewalld 有另一个名为“服务”的组件,这些服务可以在区域文件中用于管理防火墙设置中的流量规则,每个预定义的“服务”在区域文件的默认配置中使用.

dhcpv6-client

管理DHCP v6客户端的本地流量,使用udp端口546。

ssh

管理ssh服务器服务的本地通信量,并使用tcp端口22。

Samba-client

管理Windows FLES/打印机共享服务的本地通信量,并使用137(UDP)和138(UDP)端口

lpp-client

管理用于打印服务器服务的本地通信量,并使用udp端口631。

mdns

管理多播本地链路服务,并使用udp端口5353。

1.如何列出firewalld中所有可用的服务?

1
arduino复制代码$ sudo firewall-cmd --get-services
1
2
3
4
5
6
7
8
9
10
vbscript复制代码RH-Satellite-6 amanda-client amanda-k5-client amqp amqps apcupsd audit bacula bacula-client bgp bitcoin bitcoin-rpc bitcoin-testnet bitcoin-testnet-rpc 
ceph ceph-mon cfengine condor-collector ctdb dhcp dhcpv6 dhcpv6-client distcc dns docker-registry docker-swarm dropbox-lansync elasticsearch 
etcd-client etcd-server finger freeipa-ldap freeipa-ldaps freeipa-replication freeipa-trust ftp ganglia-client ganglia-master git gre 
high-availability http https imap imaps ipp ipp-client ipsec irc ircs iscsi-target isns jenkins kadmin kerberos kibana klogin kpasswd kprop 
kshell ldap ldaps libvirt libvirt-tls lightning-network llmnr managesieve matrix mdns minidlna mongodb mosh mountd mqtt mqtt-tls ms-wbt mssql 
murmur mysql nfs nfs3 nmea-0183 nrpe ntp nut openvpn ovirt-imageio ovirt-storageconsole ovirt-vmconsole plex pmcd pmproxy pmwebapi pmwebapis 
pop3 pop3s postgresql privoxy proxy-dhcp ptp pulseaudio puppetmaster quassel radius redis rpc-bind rsh rsyncd rtsp salt-master samba samba-client 
samba-dc sane sip sips slp smtp smtp-submission smtps snmp snmptrap spideroak-lansync squid ssh steam-streaming svdrp svn syncthing syncthing-gui 
synergy syslog syslog-tls telnet tftp tftp-client tinc tor-socks transmission-client upnp-client vdsm vnc-server wbem-http wbem-https wsman wsmans 
xdmcp xmpp-bosh xmpp-client xmpp-local xmpp-server zabbix-agent zabbix-server

2. 如何列出特定区域内的所有可用服务?

1
css复制代码$ sudo firewall-cmd --zone=work --list-services
1
2
3
makefile复制代码Output:

dhcpv6-client ssh

输出显示在“工作”区域中仅启用了两个服务。

3. 如何将现有服务添加到默认区域?

1
ini复制代码$ sudo firewall-cmd --add-service=samba
1
2
3
makefile复制代码Output:

success

在此示例中,我已将名为 samba 的现有服务添加到默认区域,您可以使用以下命令进行验证:

1
css复制代码$ sudo firewall-cmd --zone=public --list-services
1
2
3
makefile复制代码Output:

dhcpv6-client samba ssh

同样,我们可以将服务添加到默认区域以外的其他区域。使用以下命令:

1
csharp复制代码$ sudo firewall-cmd --zone=internal --add-service=ftp

firewalld两种模式

默认情况下,firewalld 支持两种独立的模式,永久和运行时(立即)。

当我们启动防火墙时,它会将所有永久配置文件加载到运行时中。

您进行添加或更新的任何机会都将应用于运行时配置,并且不会自动启用到永久配置。

为了使其成为永久规则,我们需要使用“--permanent”参数,为了在 firewalld 中启用这些更改,我们需要重新加载或重新启动防火墙服务。

1. 如何永久添加服务?

1
css复制代码$ sudo firewall-cmd --permanent --add-service=ftp
1
2
3
makefile复制代码Output:

success
1
css复制代码$ sudo firewall-cmd --reload
1
2
3
makefile复制代码Output:

success

请记住,无论何时使用“–permanent”标志,都不要忘记重新加载防火墙服务。

2. 如何将我的运行时设置迁移到永久设置?

1
css复制代码$ sudo firewall-cmd --runtime-to-permanent
1
2
3
makefile复制代码Output:

success

通常,我们在运行时环境中测试所有规则,一旦规则成功运行,然后我们使用“–permanent”选项使它们永久化,使用上述命令一次性将所有运行时设置迁移到永久模式,如果防火墙设置无效,则只需重新加载/重新启动防火墙服务即可使这些规则在永久配置中工作。

端口

firewalld 允许我们直接处理网络端口,美妙之处在于,甚至无需在系统中安装特定服务,我们就可以在防火墙中打开和关闭相关端口。

1. 如何在公共区域为samba服务开放端口?

1
2
3
4
5
6
7
csharp复制代码$ sudo firewall-cmd --zone=public --add-port=137/udp

$ sudo firewall-cmd --zone=public --add-port=138/udp

$ sudo firewall-cmd --zone=public --add-port=139/tcp

$ sudo firewall-cmd --zone=public --add-port=445/tcp
1
2
3
makefile复制代码Output:

success

使用上面的命令,我们已经成功打开了samba服务的端口

验证一下:

1
css复制代码$ sudo firewall-cmd --list-ports
1
2
3
bash复制代码Output:

137/udp 138/udp 139/tcp 445/tcp

成功测试后,如果您希望将这些规则作为防火墙的永久规则继续使用,则将“–permanent”标志与上述命令一起使用或使用运行时作为永久命令,不要忘记重新加载服务。

超时

Firewalld 还有一个有趣的功能叫做超时。此功能将帮助许多系统管理员在其运行时设置中添加临时规则,例如,如果用户想通过 FTP 服务从服务器下载文件。

由于这只是一次性操作,因此不需要永久规则。

下载文件可能只需要 2-5 分钟(可能会因文件大小而异)。

在我们的例子中,我们可以允许 FTP 服务 5 分钟,它会在给定的时间后自动断开连接。

1
css复制代码$ sudo firewall-cmd --zone=public --add-service=ftp --timeout=5m

我们可以以秒 (s)、分钟 (m) 或小时 (h) 为单位指定超时。

感谢您抽出宝贵时间阅读!我希望这篇文章可以帮助您通过示例了解“firewall-cmd”命令的基本用法,如果你喜欢这篇文章,请分享给其他人。

本文转载自: 掘金

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

Web与数据库连接使用增加和查询操作 Web 增加和查询操作

发表于 2021-11-25

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

Web 增加和查询操作

创建数据库

创建db_crm数据库 并且设置数据库的编码格式为UTF-8

CREATE DATABASE IF NOT EXISTS db_crm DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

创建客户数据表

1
2
3
4
5
6
7
复制代码cid      客户主键
cname   客户姓名
phone   客户手机号码
email   客户电子邮箱
url     客户公司网址
come     客户来源
remark   备注信息
1
2
3
4
5
6
7
8
9
scss复制代码CREATE TABLE  tb_customer(
          cid     int   primary key auto_increment,
          cname   varchar(200),  
          phone   varchar(25),
          email   varchar(100),
          url     varchar(100),
  come    varchar(50),
          remark  varchar(200)
)

数据库连接工具类

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
java复制代码package com.hbwl.utils;

import com.mysql.cj.jdbc.Driver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;


public class JdbcUtils {
private static final String URL="jdbc:mysql://localhost:3306/db_crm?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8";
private static final String USERNAME="root";
private static final String PASSWORD="123456";

//注册驱动
static{
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


}

//获取数据库的链接
private static Connection openConn()
{
Connection connection=null;
try {
connection = DriverManager.getConnection(URL,USERNAME,PASSWORD);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return connection;
}


//关闭数据库的链接
private static void closeConn(Connection conn,PreparedStatement ps,ResultSet rs)
{
if(rs!=null)
{
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(ps!=null)
{
try {
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(conn!=null)
{
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}


/**
* 执行增删改操作的
* @param sql 要执行的SQL语句
* @param params 这里是SQL语句的?号注入的参数
* @return 是否影响了多少行 如果影响了就为true 否则为false
*/
public static boolean executeUpdate(String sql,Map<Integer,Object> params)
{
int row=0;
//打开链接
Connection openConn = openConn();
PreparedStatement ps=null;
try {
ps = openConn.prepareStatement(sql);
Set<Entry<Integer, Object>> entry = params.entrySet();
Iterator<Entry<Integer, Object>> iterator = entry.iterator();
while(iterator.hasNext())
{
Entry<Integer, Object> next = iterator.next();
ps.setObject(next.getKey(),next.getValue());
}
row=ps.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
//关闭链接
closeConn(openConn,ps,null);
}
return row>0?true:false;

}


/**
* 执行查询的动作
* @param sql
* @param params
* @return
*/
public static ResultSet executeQuery(String sql,Map<Integer,Object> params)
{
ResultSet rs=null;
Connection openConn = openConn();
PreparedStatement ps=null;
try {
ps = openConn.prepareStatement(sql);
if(params!=null&&params.size()>0){
Set<Entry<Integer, Object>> entry = params.entrySet();
Iterator<Entry<Integer, Object>> iterator = entry.iterator();
while(iterator.hasNext())
{
Entry<Integer, Object> next = iterator.next();
ps.setObject(next.getKey(),next.getValue());
}
}
rs=ps.executeQuery();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return rs;
}
}

在web.xml中设置首页

1
2
3
4
5
6
7
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>SYSTEM</display-name>
<welcome-file-list>
<welcome-file>add.html</welcome-file>
</welcome-file-list>
</web-app>

整合静态资源

将所有的静态文件夹拷贝到项目的webapp中

新增页面

在webapp下新建add.html

新增控制Servlet

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
ini复制代码package com.hbwl.servlet;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hbwl.utils.JdbcUtils;

@WebServlet("/doAdd")
public class AddServlet extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//接收前台的参数
String cname = req.getParameter("cname");
String phone = req.getParameter("phone");
String email = req.getParameter("email");
String url = req.getParameter("url");
String come = req.getParameter("come");
String remark = req.getParameter("remark");
//调用JDBCUtils的插入方法
String sql="INSERT INTO tb_customer VALUES(newid(),?,?,?,?,?,?)";
HashMap<Integer,Object> params=new HashMap<Integer,Object>();
params.put(1,cname);
params.put(2,phone);
params.put(3,email);
params.put(4,url);
params.put(5,come);
params.put(6,remark);
//如果插入成功 则跳转到查询页面
boolean flag=JdbcUtils.executeUpdate(sql, params);
if(flag)
{
resp.sendRedirect("/list");
}
}
}

查询页面

在webapp下新建list.jsp 要转换为jsp页面 因为要在页面上编写Java代码

在JSP页面中 代码必须写在<%%>里面 后面我们会学到JSTL语句和EL表达式 是用来替代下面的
JSP页面中的JAVA语句块
<%
可以编写任意的JAVA代码
%>

#JSP页面中的表达式块
<%=Java表达式/变量值%>

例如:在jsp文件头表明文件为java类型
<%@page language=”java” contentType=”text/html; charset=UTF-8” import=”java.sql.ResultSet”%>

查询控制Servlet

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
scala复制代码package com.hbwl.servlet;

import java.io.IOException;
import java.sql.ResultSet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hbwl.utils.JdbcUtils;

@WebServlet("/list")
public class ListServlet extends HttpServlet{

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String sql="SELECT * FROM tb_customer";
ResultSet rs=JdbcUtils.executeQuery(sql, null);
req.setAttribute("resultSet", rs);
req.getRequestDispatcher("table_basic.jsp").forward(req, resp);

}

}

本文转载自: 掘金

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

SpringCloud升级之路20200x版-41 S

发表于 2021-11-25

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

本系列代码地址:github.com/JoJoTec/spr…

我们继续分析上一节提到的 WebHandler,经过将请求封装成 ServerWebExchange 的 HttpWebHandlerAdapter 之后,请求会经过 ExceptionHandlingWebHandler

image

全局 Web 处理异常处理器的接入点 - ExceptionHandlingWebHandler

之前有网友私信问过笔者,如何给 Spring Cloud Gateway 加全局异常处理器,其实和给基于 Spring-Flux 的异步 Web 服务加是一样的,都是通过实现并注册一个 WebExceptionHandler Bean:

WebExceptionHandler.java

1
2
3
kotlin复制代码public interface WebExceptionHandler {
Mono<Void> handle(ServerWebExchange exchange, Throwable ex);
}

这些 Bean,就是在 ExceptionHandlingWebHandler 被加入到整个请求处理链路中的:

ExceptionHandlingWebHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ini复制代码@Override
public Mono<Void> handle(ServerWebExchange exchange) {
Mono<Void> completion;
try {
//这里其实就是组装后面的链路,即调用后面的 FilteringWebHandler 的 handle
completion = super.handle(exchange);
}
catch (Throwable ex) {
completion = Mono.error(ex);
}

for (WebExceptionHandler handler : this.exceptionHandlers) {
completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
}
return completion;
}

从源码可以看出,这里将每个 WebExceptionHandler 作为 Mono 的异常处理 onErrorResume 加入了链路。onErrorResume 的意思是如果链路前面发生异常,则在这里捕获住异常同时调用 handler.handle(exchange, ex) 进行处理,如果使用阻塞代码理解,就相当于:

1
2
3
4
5
kotlin复制代码try {
//前面的链路
} catch(Throwable ex) {
return handler.handle(exchange, ex)
}

这里我们看到有多个 WebExceptionHandler,都会在链路后面追加 onErrorResume,其实就相当于:

1
less复制代码completion.onErrorResume(ex -> webExceptionHandler1.handle(exchange, ex)).onErrorResume(ex -> webExceptionHandler2.handle(exchange, ex)).onErrorResume(ex -> webExceptionHandler3.handle(exchange, ex))...

转换成阻塞代码理解,其实就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kotlin复制代码try {
completion
} catch(Throwable e1) {
try {
return webExceptionHandler1.handle(exchange, e1)
} catch(Throwable e2) {
try {
return webExceptionHandler2.handle(exchange, ex)
} catch(Throwable e2) {
return webExceptionHandler3.handle(exchange, ex)
//如果还有就继续叠加
}
}
}

当 WebExceptionHandler 可以处理这个异常的时候,他的 handle 方法会返回一个真正的响应,否则会返回异常,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scss复制代码public class WebExceptionHandler1 implements WebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
//如果是 ResponseStatusException 则使用异常里面的响应码和 HTTP 头填充响应的响应码和 HTTP 头
if (ex instanceof ResponseStatusException) {
ServerHttpResponse response = exchange.getResponse();
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
response.setRawStatusCode(responseStatusException.getRawStatusCode());
responseStatusException.getResponseHeaders()
.forEach((name, values) ->
values.forEach(value -> response.getHeaders().add(name, value)));
//返回响应完成
return response.setComplete();
}
//抛出异常,继续链路异常处理
return Mono.error(ex);
}
}

转换成同步代码去理解其实就是:

1
2
3
4
5
6
7
8
9
10
11
12
scss复制代码if (ex instanceof ResponseStatusException) {
ServerHttpResponse response = exchange.getResponse();
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
response.setRawStatusCode(responseStatusException.getRawStatusCode());
responseStatusException.getResponseHeaders()
.forEach((name, values) ->
values.forEach(value -> response.getHeaders().add(name, value)));
//返回响应完成
return response.setComplete();
}
//抛出异常,继续链路异常处理
throw ex;

如果大家想封装自己统一的错误响应,可以通过实现这个接口进行实现。

DefaultWebFilterChain 的链路起点 - FilteringWebHandler

接下来进入 FilteringWebHandler,注意是 org.springframework.web.server.handler.FilteringWebHandler 而不是 Spring Cloud Gateway 的 org.springframework.cloud.gateway.handler.FilteringWebHandler。在这里,会将上下文中载入的 WebFilter 拼接成 DefaultWebFilterChain,然后调用其 filter 方法:

1
2
3
4
5
6
7
8
9
10
11
kotlin复制代码private final DefaultWebFilterChain chain;

public FilteringWebHandler(WebHandler handler, List<WebFilter> filters) {
super(handler);
this.chain = new DefaultWebFilterChain(handler, filters);
}

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
return this.chain.filter(exchange);
}

Spring Cloud Gateway 的 FilteringWebHandler, 它是 Spring Cloud Gateway 的处理请求业务的起点。在这里我们即将进入整个 Spring Cloud Gateway 的 Filter 链路,包括每个路径自己的 GatewayFilter 以及全局的 GlobalGatewayFilter,都是在这里开始被处理组装成完整调用链路的。我们后面还会提到

由于我们的项目依赖中包含了 Spring Cloud Sleuth 以及 Prometheus 的依赖,所以我们这里的 WebFilter 会包括三个:

  • org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter:添加 Prometheus 相关依赖之后,会有这个 MetricsWebFilter,用于记录请求处理耗时,采集相关指标。
  • org.springframework.cloud.sleuth.instrument.web.TraceWebFilter:添加 Spring Cloud Sleuth 相关依赖之后,会有这个 TraceWebFilter。
  • org.springframework.cloud.gateway.handler.predicate.WeightCalculatorWebFilter:Spring Cloud Gateway 路由权重相关配置功能相关实现类,这个我们这里不关心。

其具体流程,我们在下一节中继续详细分析。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer:

本文转载自: 掘金

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

FPGA UART设计逻辑

发表于 2021-11-25

1 时序设计

image.png

2 串口接收模块(串转并)

image.png

2.1 模块设计

image.png
image.png

2.2 串口起始检测(高低跳变)

  • 通过检测串口接收端 uart_rxd 的下降沿来捕获起始位。一旦检测到起始位,输出一个时钟周期的脉冲 start_flag,并进入串口接收过程。串口接收状态用 rx_flag 来标志,rx_flag 为高标志着串口接收过程正在进行,此时启动系统时钟计数器 clk_cnt 与接收数据计数器 rx_cnt。
    image.png

2.3 时序设置

    1. 串口传输一位所需时长:(1/15200)
    1. 串口传输一位需要FPGA系统时钟的计数值:(1/15200)/(1/50000000)
      image.png
      image.png
      image.png
    1. 当脉冲信号 start_flag 到达时,进入接收过程,计数到9位清除rx_flag

      image.png

    1. 根据接收数据计数器来寄存UART接收端口数
      image.png
      image.png
    1. 数据接收完毕后给出标志信号uart_done,并寄存输出接收到的数据
      image.png

3 串口发送模块(并转串)

image.png

3.1 模块设计

image.png

3.2 串口发送起始检测(高低跳变)

image.png

3.3 时序设置

  • 当脉冲信号 en_flag 到达时,寄存待发送的数据,并进入发送过程
    image.png
  • 进入发送过程后,启动系统时钟计数器
    image.png
    image.png
  • 根据发送数据计数器来给 uart 发送端口赋值
    image.png

本文转载自: 掘金

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

Java集合学习(三)——EnumSet枚举类

发表于 2021-11-25

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

前言

1
复制代码 大家好,我是程序猿小白 GW_gw,很高兴能和大家一起学习进步。

关于Java的集合体系我们已经了解了,接下来我们就开始具体类的学习。

以下内容部分来自于网络,如有侵权,请联系我删除,本文仅用于学习交流,不用作任何商业用途。

摘要

1
复制代码 本文主要介绍Java集合的EnumSet枚举类

1.1 EnumSet类

EnumSet是一个为枚举类型专用的抽象集合类。在EnumSet不允许加入null元素。EnumSet的集合是有序的,元素顺序和实例化EnumSet集合时的枚举类的元素定义顺序一致。

EnumSet在内部以位向量的形式存储,这种存储表现形式非常紧凑且高效,因此此类的空间和时间性能很好。

1.1.1 获得EnumSet类的对象

EnumSet类是一个抽象类,不能直接new一个对象,但是可以使用EnumSet类的静态方法来获得EnumSet类的实例对象。

方法 描述
static EnumSet allOf(Class elementType) 创建一个包含指定元素类型的所有元素的Enumset。
static EnumSet complementOf(EnumSet s) 创建一个其元素类型与指定Enumset 相同元素类型的Enumset,包含此枚举类剩余的枚举元素。(即两个EnumSet合起来包含的枚举类的所有元素。)
static EnumSet copyOf(EnumSets) 创建一个其元素类型与指定枚举 set 相同的枚举 set,相当于一个副本。
static EnumSet copyOf(Collection c) 创建一个从指定 collection 初始化的Enumset。
static EnumSet noneOf(Class elementType) 创建一个具有指定元素类型的空枚举 set。
static EnumSet of(E first, E… rest) 创建一个最初包含指定元素的枚举 set。从first到rest,可以是一个或多个。
static EnumSet range(E from, E to) 创建一个最初包含由两个指定端点所定义范围内的所有元素的枚举 set。

实例展示:

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
csharp复制代码enum WEEKDAY{
  MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
}
/**
* @author Poison
*/
public class EnumSetClass {
  public static void main(String[] args) {
​
      EnumSet enumSet = EnumSet.allOf(WEEKDAY.class);
      //此集合包含枚举类中得到所有元素
      System.out.println(enumSet);
      EnumSet enumSet1 = EnumSet.noneOf(WEEKDAY.class);
      System.out.println("=================================");
      //此时该枚举集合为空,手动添加元素
      enumSet1.add(WEEKDAY.MONDAY);
      System.out.println(enumSet1);
      //此集合包含MONDAY枚举元素
      EnumSet enumSet2 = EnumSet.of(WEEKDAY.MONDAY);
      System.out.println("=================================");
      System.out.println(enumSet2);
      //此集合包含除了enumSet1中的其他的WEEKDAY中的枚举元素
      EnumSet enumSet3 = EnumSet.complementOf(enumSet1);
      System.out.println("=================================");
      System.out.println(enumSet3);
      //此集合包含MONDAY到FRIDAY的枚举元素
      EnumSet enumSet4 = EnumSet.range(WEEKDAY.MONDAY,WEEKDAY.FRIDAY);
      System.out.println("=================================");
      System.out.println(enumSet4);
​
​
​
  }
}

image-20211125230233263

小结

以上就是关于EnumSet枚举类的一些用法介绍和实例展示,如有不正之处,欢迎留言评论。

本文转载自: 掘金

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

Go&Java算法之数组中数字出现的次数 数组中数字出现的次

发表于 2021-11-25

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

数组中数字出现的次数

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入:nums = [3,4,3,3]

输出:4

示例 2:

输入:nums = [9,1,7,9,7,9,7]

输出:1

限制:

1 <= nums.length <= 10000
1 <= nums[i] < 2^31

题解

方法一:有限状态自动机——Java

各二进制位的 位运算规则相同 ,因此只需考虑一位即可。如下图所示,对于所有数字中的某二进制位 1 的个数,存在 3 种状态,即对 3 余数为 0, 1, 2 。

若输入二进制位 1 ,则状态按照以下顺序转换;

若输入二进制位 0 ,则状态不变。

以上是对数字的二进制中 “一位” 的分析,而 int 类型的其他 31 位具有相同的运算规则,因此可将以上公式直接套用在 32 位数上。

遍历完所有数字后,各二进制位都处于状态 00 和状态 01 (取决于 “只出现一次的数字” 的各二进制位是 1 还是 0 ),而此两状态是由 one 来记录的(此两状态下 twos 恒为 0 ),因此返回 ones 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Java复制代码class Solution {
public int singleNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int res = 0;
for (int i = 0; i < 32; i++) {
int cur = 0;
int div = 1;
for (int j = 0; j < nums.length; j++) {
if ((nums[j] & (div << i)) > 0) {
cur++;
}
}
if (cur % 3 == 1) {
res ^= div << i;
}
}
return res;
}
}

时间复杂度 O(N) : 其中 N 位数组 nums 的长度;遍历数组占用 O(N) ,每轮中的常数个位运算操作占用 O(1)O(32×3×2)=O(1) 。

空间复杂度 O(1) : 变量 onesones , twostwos 使用常数大小的额外空间。

方法一:有限状态自动机——Go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go复制代码class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int i = 0; i < 32; i++) {
// 对于int每一位
int bit = 0;
// 计算该位上的和
for (int num : nums) {
bit += ((num >> i) & 1);
}
// 对3取余即为res在该位的值
res += ((bit % 3) << i);
}
return res;
}
}

本文转载自: 掘金

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

SpringBoot项目配置文件

发表于 2021-11-25

SpringBoot项目中配置文件是一个重要的组成部分,通过配置文件统一定义属性配置,并在程序中使用可以让程序更简洁直观。

  1. SpringBoot配置项

1.1 官方提供配置项

SpringBoot官方文档:Spring Boot配置项

SpringBoot提供了丰富的配置项,实际项目开发中常用到的有:

  • Server,以server为前缀的如服务地址、端口号、jetty、netty、servlet、tomcat等
  • Core,包括logging配置、以spring为前缀的config、message、profile、quartz、task等
  • Cache,缓存相关配置,如spring.cache.jcache、spring.cache.redis相关
  • Mail,邮件相关配置项
  • Security,支持Spring Security相关配置项
  • Devtools,项目中使用Devtools工具时配置内容
  • Testing,单元测试用到的配置

1.2 自定义配置项

除了官方提供的配置项外,还可以根据项目的需要在配置文件中自定义配置项,并设置对应值,只要符合SpringBoot配置文件要求的内容,就可以根据相关注解和方法获取配置内容。

而官方提供的配置项多数已经在SpringBoot自动配置中使用,当项目中引入相关框架工具后,SpringBoot会根据自动配置内容加载默认的属性值。

  1. SpringBoot项目配置文件

2.1 配置文件类型

官方支持并提供方法获取配置内容的文件类型有两种:

  1. application.properties
  2. application.yml

如果想要自定义项目配置文件的路径和名称,需要在项目启动时使用命令参数来指定程序运行时选择的配置文件: --spring.config.name=[path]/myproject.properties。

实际开发中不建议对配置文件的命名和位置进行更改,这也是SpringBoot所约定的配置,文件名为application,存放位置为resources(classpath)。

2.2 properties配置文件

properties配置文件对应Properties类对象,基于Map集合,以键值对形式存放key和value,两者类型都是字符串,使用时根据指定字符串key获取value。

1
2
3
properties复制代码# 注释
server.servlet.context-path=/spring-boot
server.port=8088

2.3 yml配置文件

yml类型文件是SpringBoot使用的新的配置文件类型,在配置内容较多时使用更方便简洁,yml文件在配置内容书写上与properties文件略有不同。

使用yml文件定义配置时需要注意:

  • yml文件中是以对象的结构定义属性,使用空格缩进来控制属性的层级
  • 定义属性值时需要在属性:之后增加一个空格
  • yml文件中对于大小写是敏感的
  • 同样使用#来代表当前行作为文件注释
  • 定义数组时可以使用 [] 或者多行的 -
  • 使用 ${key} 引用已存在key对应的value
1
2
3
4
5
6
7
8
9
10
11
12
yml复制代码# 注释
server:
port: 8087
​
# 定义数组类型的两种方式
names: [tom,lily]
codes:
- band
- tand
​
# 引用存在参数
newPort: ${server.port}
  1. SpringBoot中读取配置

在配置文件中定义好配置项和对应值后,就可以在项目启动获取配置的内容,获取自定义属性值的方式是多样的。

3.1 @Value注解

使用@Value注解标注在字段上,就可以将指定key对应的value赋值给字段。

  • 使用${}指定配置项的key值
  • 可以在任意的java bean中使用该注解
1
2
java复制代码@Value("${server.port}")
private String port;

3.2 @ConfigurationProperties注解

@ConfigurationProperties注解用来标注一个类,并将该类对的属性与配置文件中的一系列配置项关联,将配置值通过容器赋值到类属性中。

  • @ConfigurationProperties标注类,并用prefix指定类对应配置文件key的前缀信息
  • 类需要使用@Component等标注,交给容器管理完成自动注入
  • 类属性需要由getter/setter方法,否则无法注入成功
1
2
3
4
5
6
7
java复制代码//使用时直接注入ServerConfig对象获取对象属性值即可
@Component
@ConfigurationProperties(prefix = "server")
@Data
public class ServerConfig {
   private String port;
}

3.3 Environment

Environment对象是SpringBoot启动后自动配置完成的项目环境信息,可以从Environment对象中获取在配置文件中配置的内容。

  • 要先注入容器中的Environment
  • 使用对象的getProperty()方法根据key获取value
1
2
3
4
java复制代码@Autowired
private Environment environment;
//获取配置项内容
environment.getProperty("server.port")

3.4 Properties

Properties对象是SpringBoot配置文件对应的对象,在项目启动自动配置时自动注入所有的配置内容,可以定义Properties对象来读取指定的proiperties文件中的内容。

使用Properties读取配置文件

  1. SpringBoot项目的多环境切换配置

项目开发的阶段包括开发、测试、生产上线等步骤,为了分离不同环境,往往需要针对各个环境定义专用的配置文件信息,并在主配置文件定义当前运行环境来读取指定配置信息。具体的方法有:

  • 设置读取指定配置文件:spring.profiles.active=dev
  • IDEA中使用命令参数启动项目时指定选择环境
    • IDEA的VM options指定:-Dspring.profiles.active=dev
    • IDEA的Program arguments指定:--spring.profiles.active=dev
  • 项目打Jar包时使用命令指定
    • java -jar xxx.jar --spring.profiles.active=dev

本文转载自: 掘金

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

1…186187188…956

开发者博客

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