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

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


  • 首页

  • 归档

  • 搜索

PostgreSql 获取对象 oid

发表于 2021-11-29

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

在使用 PostgreSql 数据库时,发现很多时候需要查询对象的 oid, 本文记录下查询对象的 oid 的方法,后续发现更方便的方式会再更新……

获取数据库的 oid

1
2
3
4
5
6
7
8
9
10
11
sql复制代码--注意大小写敏感,要用小写
chis=# select oid,datname from pg_database where datname='syd';
oid | datname
-------+---------
41351 | syd
(1 row)

chis=# select oid,datname from pg_database where datname='SYD';
oid | datname
-----+---------
(0 rows)

获取用户或角色的 oid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sql复制代码--方法一:大小写敏感,要用小写
chis=# select oid,rolname from pg_authid where rolname='syd';
oid | rolname
-------+---------
66934 | syd
(1 row)

chis=# select oid,rolname from pg_authid where rolname='SYD';
oid | rolname
-----+---------
(0 rows)
--方法二:大小写不敏感,用大小写均可
chis=# select 'syd'::regrole::oid;
oid
-------
66934
(1 row)

chis=# select 'SYD'::regrole::oid;
oid
-------
66934
(1 row)

获取 schema 的 oid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sql复制代码--方法一:大小写敏感,要用小写
chis=# select oid,nspname from pg_namespace where nspname='comm';
oid | nspname
-------+---------
16398 | comm
(1 row)

chis=# select oid,nspname from pg_namespace where nspname='COMM';
oid | nspname
-----+---------
(0 rows)

--方法二:大小写不敏感,用大小写均可
chis=# select 'comm'::regnamespace::oid;
oid
-------
16398
(1 row)

chis=# select 'COMM'::regnamespace::oid;
oid
-------
16398
(1 row)

获取表,索引等对象的 oid

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
sql复制代码--方法一:大小写敏感,要用小写
---查询表的 oid
chis=# select oid,relname from pg_class where relname='account';
oid | relname
-------+---------
66640 | account
(1 row)

chis=# select oid,relname from pg_class where relname='ACCOUNT';
oid | relname
-----+---------
(0 rows)
--查询索引的 oid
chis=# select oid,relname from pg_class where relname='pk_account_id';
oid | relname
-------+---------------
66646 | pk_account_id
(1 row)

chis=# select oid,relname from pg_class where relname='PK_ACCOUNT_ID';
oid | relname
-----+---------
(0 rows)
--方法二:大小写不敏感,用大小写均可(注意需要指定schema名,不然表在当前schema不可见时,会报错)
---查询表的 oid
chis=# select 'account'::regclass::oid;
ERROR: relation "account" does not exist
LINE 1: select 'account'::regclass::oid;
^
chis=# select 'comm.account'::regclass::oid;
oid
-------
66640
(1 row)

chis=# select 'COMM.ACCOUNT'::regclass::oid;
oid
-------
66640
(1 row)
---查询索引的 oid
chis=# select 'pk_account_id'::regclass::oid;
ERROR: relation "pk_account_id" does not exist
LINE 1: select 'pk_account_id'::regclass::oid;
^
chis=# select 'comm.pk_account_id'::regclass::oid;
oid
-------
66646
(1 row)

chis=# select 'COMM.PK_ACCOUNT_ID'::regclass::oid;
oid
-------
66646
(1 row)

获取函数的 oid

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
sql复制代码--方法一:大小写敏感,要用小写
chis=# select oid,proname from pg_proc where proname='out_tally_pepole_count';
oid | proname
-------+------------------------
16464 | out_tally_pepole_count
(1 row)

chis=# select oid,proname from pg_proc where proname='OUT_TALLY_PEPOLE_COUNT';
oid | proname
-----+---------
(0 rows)
--方法二:大小写不敏感,用大小写均可(注意需要指定schema名,不然表在当前schema不可见时,会报错)
chis=# select 'out_tally_pepole_count'::regproc::oid;
ERROR: function "out_tally_pepole_count" does not exist
LINE 1: select 'out_tally_pepole_count'::regproc::oid;
^
chis=# select 'finance.out_tally_pepole_count'::regproc::oid;
oid
-------
16464
(1 row)

chis=# select 'FINANCE.OUT_TALLY_PEPOLE_COUNT'::regproc::oid;
oid
-------
16464
(1 row)

本文转载自: 掘金

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

OpenCV实现图像的几何变换

发表于 2021-11-29

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

图像的几何变换

几何变换主要包括缩放、平移、旋转、仿射变换、透视变换和图像裁剪等。执行这些几何变换的两个关键函数是 cv2.warpAffine() 和 cv2.warpPerspective()。

cv2.warpAffine() 函数使用以下 2 x 3 变换矩阵来变换源图像:

dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)dst(x,y)=src(M_{11}x+M_{12}y+M_{13}, M_{21}x+M_{22}y+M_{23})dst(x,y)=src(M11​x+M12​y+M13​,M21​x+M22​y+M23​)
cv2.warpPerspective() 函数使用以下 3 x 3 变换矩阵变换源图像:

dst(x,y)=src(M11x+M12y+M13M31x+M32y+M33,M21x+M22y+M23M31x+M32y+M33)dst(x,y)=src(\frac {M_{11}x+M_{12}y+M_{13}} {M_{31}x+M_{32}y+M_{33}}, \frac {M_{21}x+M_{22}y+M_{23}} {M_{31}x+M_{32}y+M_{33}})dst(x,y)=src(M31​x+M32​y+M33​M11​x+M12​y+M13​​,M31​x+M32​y+M33​M21​x+M22​y+M23​​)
接下来,我们将了解最常见的几何变换技术。

缩放图像

缩放图像时,可以直接使用缩放后图像尺寸调用 cv2.resize():

1
2
python复制代码# 指定缩放后图像尺寸
resized_image = cv2.resize(image, (width * 2, height * 2), interpolation=cv2.INTER_LINEAR)

除了上述用法外,也可以同时提供缩放因子 fx 和 fy 值。例如,如果要将图像缩小 2 倍:

1
2
python复制代码# 使用缩放因子
dst_image = cv2.resize(image, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)

如果要放大图像,最好的方法是使用 cv2.INTER_CUBIC 插值方法(较耗时)或 cv2.INTER_LINEAR。如果要缩小图像,一般的方法是使用 cv2.INTER_LINEAR。
OpenCV 提供的五种插值方法如下表所示:

插值方法 原理
cv2.INTER_NEAREST 最近邻插值
cv2.INTER_LINEAR 双线性插值
cv2.INTER_AREA 使用像素面积关系重采样
cv2.INTER_CUBIC 基于4x4像素邻域的3次插值
cv2.INTER_LANCZOS4 正弦插值

显示缩放后的图像:

1
2
3
4
5
6
7
8
9
10
11
python复制代码def show_with_matplotlib(color_img, title, pos):
# Convert BGR image to RGB
img_RGB = color_img[:,:,::-1]
ax = plt.subplot(1, 3, pos)
plt.imshow(img_RGB)
plt.title(title,fontsize=8)
# plt.axis('off')
show_with_matplotlib(image, 'Original image', 1)
show_with_matplotlib(dst_image, 'Resized image', 2)
show_with_matplotlib(dst_image_2, 'Resized image 2', 3)
plt.show()

可以通过坐标系观察图片的缩放情况:

图片缩放

平移图像

为了平移对象,需要使用 NumPy 数组创建 2 x 3 变换矩阵,其中提供了 x 和 y 方向的平移距离(以像素为单位):

1
python复制代码M = np.float32([[1, 0, x], [0, 1, y]])

其对应于以下变换矩阵:

[10tx01ty]\begin{bmatrix}
1 & 0 & t_x \
0 & 1 & t_y
\end{bmatrix}[10​01​tx​ty​​]
创建此矩阵后,调用 cv2.warpAffine() 函数:

1
python复制代码dst_image = cv2.warpAffine(image, M, (width, height))

cv2.warpAffine() 函数使用提供的 M 矩阵转换源图像。第三个参数 (width, height) 用于确定输出图像的大小。

例如,如果图片要在 x 方向平移 200 个像素,在 y 方向移动 30 像素:

1
2
3
python复制代码height, width = image.shape[:2]
M = np.float32([[1, 0, 200], [0, 1, 30]])
dst_image_1 = cv2.warpAffine(image, M, (width, height))

平移也可以为负值,此时为反方向移动:

1
2
python复制代码M = np.float32([[1, 0, -200], [0, 1, -30]])
dst_image_2 = cv2.warpAffine(image, M, (width, height))

显示图片如下:

图片平移

旋转图像

为了旋转图像,需要首先使用 cv.getRotationMatrix2D() 函数来构建 2 x 3 变换矩阵。该矩阵以所需的角度(以度为单位)旋转图像,其中正值表示逆时针旋转。旋转中心 (center) 和比例因子 (scale) 也可以调整,使用这些元素,以下方式计算变换矩阵:

[αβ(1−a)⋅center.x−β⋅center.y−βαβ⋅center.x−(1−α)⋅center.y]\begin{bmatrix}
\alpha & \beta & (1-a)\cdot center.x-\beta\cdot center.y \
-\beta & \alpha & \beta\cdot center.x-(1-\alpha)\cdot center.y
\end{bmatrix}[α−β​βα​(1−a)⋅center.x−β⋅center.yβ⋅center.x−(1−α)⋅center.y​]
其中:

α=scale⋅cosθ,β=scale⋅sinθ\alpha=scale\cdot cos\theta, \beta=scale\cdot sin\thetaα=scale⋅cosθ,β=scale⋅sinθ
以下示例构建 M 变换矩阵以相对于图像中心旋转 180 度,缩放因子为 1(不缩放)。之后,将这个 M 矩阵应用于图像,如下所示:

1
2
3
python复制代码height, width = image.shape[:2]
M = cv2.getRotationMatrix2D((width / 2.0, height / 2.0), 180, 1)
dst_image = cv2.warpAffine(image, M, (width, height))

接下来使用不同的旋转中心进行旋转:

1
2
python复制代码M = cv2.getRotationMatrix2D((width/1.5, height/1.5), 30, 1)
dst_image_2 = cv2.warpAffine(image, M, (width, height))

显示旋转后的图像:

旋转图像

2.4 图像的仿射变换

在仿射变换中,首先需要使用 cv2.getAffineTransform() 函数来构建 2 x 3 变换矩阵,该矩阵将从输入图像和变换图像中的相应坐标中获得。最后,将 M 矩阵传递给 cv2.warpAffine():

1
2
3
4
python复制代码pts_1 = np.float32([[135, 45], [385, 45], [135, 230]])
pts_2 = np.float32([[135, 45], [385, 45], [150, 230]])
M = cv2.getAffineTransform(pts_1, pts_2)
dst_image = cv2.warpAffine(image_points, M, (width, height))

仿射变换是保留点、直线和平面的变换。此外,平行线在此变换后将保持平行。但是,仿射变换不会同时保留像素点之间的距离和角度。

可以通过以下图像观察仿射变换的结果:

仿射变换

2.5 图像的透视变换

为了进行透视变换,首先需要使用 cv2.getPerspectiveTransform() 函数创建 3 x 3 变换矩阵。该函数需要四对点(源图像和输出图像中四边形的坐标),函数会根据这些点计算透视变换矩阵。然后,将 M 矩阵传递给 cv2.warpPerspective() :

1
2
3
4
python复制代码pts_1 = np.float32([[450, 65], [517, 65], [431, 164], [552, 164]])
pts_2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv2.getPerspectiveTransform(pts_1, pts_2)
dst_image = cv2.warpPerspective(image, M, (300, 300)

透视变换效果如下所示:

透视变换

2.6 裁剪图像

可以使用 NumPy 切片裁剪图像:

1
python复制代码dst_image = image[80:200, 230:330]

裁剪结果如下所示:

图像裁剪

本文转载自: 掘金

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

SpringBootAOP自定义注解记录项目业务日志 序 E

发表于 2021-11-29

序

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

先引入aop依赖

1
2
3
4
5
6
xml复制代码<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.5.2</version>
</dependency>

关于AOP 建议读者好好学习理解哈,这里不介绍了,就一句话:很重要很重要!!!

创建编写相关代码

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
less复制代码/**
* 自定义 log注解
*
* @author Smile
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* 记录业务名称
*
* @return 业务名称
*/
String value() default "";
}

我这边建立的是方法层级的

切面类

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
java复制代码@Resource
private SysLogService logService;
private SysLogPO sysLogPO = new SysLogPO();

/**
* 这个是切点的意思,从什么地方切入: 可以是具体的类或者注解 ,也可以是模糊的类路径 * *..*Api.*(..)
*/
@Pointcut("@annotation(com.smile.ssm.aop.annotation.SysLog)")
public void annotationPointcut() {
}

/**
* 前置通知, 在方法执行之前执行
*
* @param joinPoint xx
*/
@Before("annotationPointcut()")
public void beforePointcut(JoinPoint joinPoint) {
System.out.println("beforePointcut");

}

/**
* 环绕通知, 围绕着方法执行
*
* @param point
* @return
* @throws Throwable
*/
@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
System.out.println("doAround");
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
//这个是获取调用的方法类型:get post ...
String method1 = request.getMethod();
String url = request.getRequestURL().toString();
sysLogPO.setMethod(url);
sysLogPO.setDeleted(false);
sysLogPO.setCreateTime(LocalDateTime.now());
//获得执行方法的类名
String targetName = point.getTarget().getClass().getName();
//获得执行方法的方法名
String methodName = point.getSignature().getName();
//获取切点方法的所有参数类型
Object[] arguments = point.getArgs();
try {
Class targetClass = Class.forName(targetName);
//获取公共方法,不包括类私有的
Method[] methods = targetClass.getMethods();
String value = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
//对比方法中参数的个数
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
value = method.getAnnotation(SysLog.class).value();
break;
}
}
}
sysLogPO.setBusinessName(value);
} catch (Exception e) {
e.printStackTrace();
}

logService.save(sysLogPO);
log.info("===请求开始, 各个参数, url: " + url + ", method: " + method1 + ", uri:" + request.getRequestURI() + ", params:" + request.getQueryString());
if (arguments != null) {
for (Object argument : arguments) {
log.info("===接口入参:" + JSON.toJSONString(argument));
}
}
return point.proceed();

}

/**
* 返回通知, 在方法返回结果之后执行
*
* @param joinPoint xx
*/
@AfterReturning("annotationPointcut()")
public void doAfterReturning(JoinPoint joinPoint) {
System.out.println("doAfterReturning");
}

/**
* 异常通知, 在方法抛出异常之后
*/
@AfterThrowing("annotationPointcut()")
public void doAfterThrowing(JoinPoint joinPoint) {
System.out.println("doAfterThrowing");
}

/**
* 后置通知, 在方法执行之后执行
*/
@After("annotationPointcut()")
public void doAfter(JoinPoint joinPoint) {
System.out.println("doAfter");
}
1
2
3
4
5
6
7
8
less复制代码@Pointcut

1)execution(* *(..))
//表示匹配所有方法
2)execution(public * com.smile.ssm.service.*Api*(..))
//表示匹配com.smile.ssm.UserService中所有的公有方法
3)execution(* com.smile.ssm..*.*(..))
//表示匹配com.savage.server包及其子包下的所有方法
1
2
3
4
5
6
java复制代码/**
* 搭配切点使用前置通知, 在方法执行之前执行
*
* @param joinPoint xx
*/
@Before("annotationPointcut()")
1
2
3
4
5
6
7
8
java复制代码/**
* 环绕通知, 围绕着方法执行
*
* @param point
* @return
* @throws Throwable
*/
@Around("annotationPointcut()")
1
2
3
4
5
markdown复制代码/**
* 返回通知, 在方法返回结果之后执行
*
* @param joinPoint xx
*/
1
2
3
arduino复制代码/**
* 异常通知, 在方法抛出异常之后
*/
1
2
3
arduino复制代码/**
* 后置通知, 在方法执行之后执行
*/
关于几个增强注解的执行循序
正常情况: doAround->beforePointcut->doAfterReturning->doAfter

image.png

异常情况: doAround->beforePointcut->doAfterThrowing->doAfter

1638233844(1).jpg

特殊情况:

after 里面出现错误的话将 AfterThrowing捕获不到 它只对于被切入的方法所以这里需要注意避免出现多条日志的情况哦。

上面切面类测试代码中写了挺多东西的就是为了测试过程以及搭配:

日志可选择:

  1. @Before + @After
  2. @Around

上面两种随便搭配就好!!!

Test

效果:调用业务的日志存库了,并且系统能够打印调用日志!!!
image.png

image.png

image.png

END

完美完成aop自定义业务注解啦,下一篇:项目引入服务器端logback日志管理。

源码地址:gitee.com/smile_lx/ss…

今天到洗牙,哇好难受呀,一嘴血味>!-!<

本文转载自: 掘金

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

Python matplotlib 绘制流线图 复习回顾 1

发表于 2021-11-29

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

复习回顾

在Python关于绘图,Mlab提供开源的matplotlib模块,不仅可以绘制折线图、柱状图、散点图等常规图外,还支持绘制量场图、频谱图、提琴图、箱型图等特殊图,例举往期文章可前往查看详情。

  • specgram()方法绘制频谱图用于振幅频谱:matplotlib 绘制频谱图
  • boxplot()方法绘制箱型图用于数据分布:matplotlib 绘制箱型图
  • quiver()方法绘制量场图用于电磁场分析:matplotlib 绘制量场图
  • violinplot()绘制展示数据分布和概率情况:matplotlib 绘制提琴图

我们日常生活中经常会关注天气预报,在换季的时候,播报员会讲解气流流动情况。在天气预报过程中,气象专家们会根据流线图绘制的气流情况,来预测当地的天气情况。

流线图.png

本期,我们将学习matplotlib.pyplot.streamplot()方法相关属性的学习,let’s go~

  1. 流线图概述

  • 什么是流线图?

+ 流线图通过流线和箭头的组合绘制,来表示某一时段流线的运行情况、
+ 流线图上的箭头表示流向,流线上的形状表示流强度
+ 流线图可分为气流图、等风速线、变高图等
+ 流线图中的流线可以合并、汇合、分交,但不能交叉
  • 流线图应用场景

+ 流线图通常用于气象学中研究风速、气流、洋流的流向情况
+ 流程图是风场分析的重要图表,流线的稀密度与风速大小成正比
  • 获取流线图方法

1
2
python复制代码import matplotlib.pyplot as plt 
plt.streamplot(x,y,u,v)
  1. 流线图属性

  • 设置流线图密度

+ 关键字:density
+ 默认值为:1
+ 取值类型为:浮点型或者元组
+ 控制流线图密度,当density=1时,网格会被划分为30\*30网格
+ 对于设置每个方向上密度,可以使用元组(x,y)
  • 设置流线宽度

+ 关键字:linewidth
+ 取值类型为:浮点型或者二维数组
+ 使用二维数组,可以改变流线在网格上的线宽
+ 阵列的形状必须要与u、v相同
  • 设置流线颜色

+ 关键字:color
+ 取值可为:
    - 表示颜色的英文单词:如绿色"g"
    - 表示颜色单词的简称如:红色"r",黄色"y"
    - RGB格式:十六进制格式如"#88c999";(r,g,b)元组形式
    - 可以转入颜色列表
+ 当使用cmap时,则需要color设置为二维数组,否则无效
  • 设置流线缩放

+ 关键字:norm
+ 默认为将流线拉伸到(0,1)
+ 仅在颜色为数组时使用
  • 设置流线颜色系

+ 关键字:cmap
+ 取值形式为:颜色表\_r
+ 可取值常用的有:'Accent', 'Accent\_r', 'Blues', 'Blues\_r', 'BrBG', 'BrBG\_r', 'BuGn', 'BuGn\_r', 'BuPu', 'BuPu\_r', 'CMRmap', 'CMRmap\_r', 'Dark2', 'Dark2\_r', 'GnBu', 'GnBu\_r', 'Greens'
  1. 绘制流线图步骤

  • 导入matplotlib.pyplot类
1
python复制代码import matplotlib.pyplot as plt
  • 调用numpy库arange()、random()、randint()等准备x,y,u,v数据
+ x,y:一维数组/二维数组
+ u,v:二维数组
+ 当为二维数组,可以通过np.meshgrid(x,y),np.mgrid()创建
1
2
3
4
python复制代码x = np.arange(1,10)
y = np.arange(1,10)

u,v = np.meshgrid(np.sin(x),np.sin(y))
  • 调用pyplot.streamplot()绘制流线图
1
python复制代码plt.streamplot(x,y,u,v,density=[0.5,1])
  • 调用pyplot.show()渲染显示出流线图
1
python复制代码plt.show()

image.png

  • 设置linewidth、color、cmap属性绘制流线图
1
python复制代码plt.streamplot(x,y,u,v,density=[0.5,1],color=u,cmap="Accent_r",linewidth=3)

image.png

  1. 小试牛刀

我们学习了关于绘制流线图相关属性,我们来实操一下控制流线的起点数据

  • 调用np.mgrid[]定义x,y二维数据
  • 调用pyplot.streamplot()方法绘制流线图
  • 调用pyplot.plot()方法绘制折线图,使用marker属性标记
1
2
3
4
5
6
7
8
9
python复制代码y,x= np.mgrid[-3:3:100j, -3:3:100j]
u = -1-x**2+y
v = 1+x-y**2

seed_points = np.array([[-2, -1, 0, 1, 2, -1], [-2, -1, 0, 1, 2, 2]])
plt.streamplot(x,y,u,v,density=0.6,color=u,cmap="autumn",linewidth=1,start_points=seed_points.T)
plt.plot(seed_points[0],seed_points[1],"^",color="b")

plt.show()

image.png

总结

本期,我们对matplotlib.pyplot提供streamplot()方法绘制流线图相关属性的学习。流线图通常使用在气象学中,研究气流变化情况。

以上是本期内容,欢迎大佬们点赞评论,下期见~

本文转载自: 掘金

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

入行五年的普通程序员,能赚100万吗? 我采访了我四个朋友

发表于 2021-11-29

​

入行五年的普通程序员,能赚100万吗? 你觉得可能吗?

今天我是突然刷到这一类型的文章——教你程序员快速赚100万。我看到了如下的文章,我突然自闭了,为了验证事实,我采访了我4个朋友。

今天我们的关键词是“普通“,“入行五年”,”100万“,于是我开始了我的采访!看看真实的普通程序员都是如何赚钱和如何看待这个问题的。也欢迎程序员们留言讨论。

​

我采访了我四个朋友

都是不同岗位的程序员,有后端,有全栈,也有架构师,来看看真实的情况,直接上微信截图。

第一位朋友

是比较厉害的程序员40多岁,90年代入行互联网,国内非常牛逼的985毕业,在深圳打拼,后来赚够了就躺尸状态的架构师。来看看他是怎么评论的。

​

​

然后我把那些说能教程序员如何快速赚100万的文章发给了他看,文章有说到:自己开公司这块,然后我朋友直接如图说了一句。

​

确实,我身边好些程序员自己合伙创业,疫情来了,市场和技术不对等,很多创业都面临资金问题。

第二位朋友

普通学校,入行5年后端程序员,27岁,坐标重庆

认为要看你所在的城市和你所接的项目,可以决定你是否能赚到钱

​

第三位朋友

在国内某知名游戏公司,做到项目负责人,普通学校,入行5年,26岁;认为100万是可以的,但是刚开始的5年最好去到一线城市打拼。

​

第四位朋友

入行2年,24岁,坐标成都,从广东跳槽回成都的从测试转后端的程序员。目前每天都在苦逼的学习,也同时在准备着研究生的考试来提高自己的身价。

​

王健林1个亿小目标绝大多数人是遥不可及的,

但是,100万还是可以乞及的。比如做一些副业私活,私活的方式,基本上都是熟人对接,其次就是在网上找,比如猪八戒网

可以在这上面接设计、logo、策划、海报、UI的单子。

要么就是把你的一些源码上传到51aspx源码平台,卖源码和提供附加服务赚钱。

​

快速赚钱还是很难得。

​

本文转载自: 掘金

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

翻译翻译,什么是继承! 继承

发表于 2021-11-29

继承

  1. 什么是继承

继承是面向对象三大特征之一。

从字面意思理解就是“通过继承一个人的财产,从一无所有变得无所不有。”

这让我想起了电影《西虹市首富》,处于人生低谷期的王多鱼偶然间继承了二爷遗产,从此走向人生巅峰。

面向对象的继承其实来源于现实生活,子类通过继承父类,获取父类的属性和方法。

  1. 为什么要用继承

先看一下下面的例子:

  1. 新建一个学生类
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
java复制代码public class Student {
// 姓名
private String name;
// 年龄
private int age;
// 性别 0-女 1-男
private int sex;

public Student() {
}
public Student(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void eat(){
System.out.println("学生要吃饭");
}
public void study(){
System.out.println("学生要学习");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}
}
  1. 新建一个教师类
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
java复制代码public class Teacher {
// 姓名
private String name;
// 年龄
private int age;
// 性别 0-女 1-男
private int sex;

public Teacher() {
}
public Teacher(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void eat(){
System.out.println("老师要吃饭");
}
public void teach(){
System.out.println("老师要讲课");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}
}

我们发现教师类和学生类都有姓名、年龄、性别的特征,也都需要吃饭。两个类都写一遍相同的属性和方法是不是“脱裤子放屁,多此一举?”

如果几千个类都有这些相同的特征,哪怕是复制粘贴是不是也累死人?

那可不可以把这些相同的特征都放在同一个类中,让其他类继承它?就相当于也拥有了这些特征?

答案是可以的。

  1. 继承的使用

3.1 语法格式

继承关键字:extends

1
2
3
java复制代码class 类名 extends 父类名{
方法+属性
}

3.2 使用继承

  1. 新建一个 Person 类,将学生类和教师类的相同特征都放里面。
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
java复制代码public class Person {
// 姓名
private String name;
// 年龄
private int age;
// 性别 0-女 1-男
private int sex;

public Person() {
}

public Person(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

public void eat(){
System.out.println("人要吃饭");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}
}
  1. 新建一个学生类继承 Person 类
1
2
3
4
5
java复制代码public class Student extends Person{
private void study(){
System.out.println("学生要学习");
}
}
  1. 新建一个教师类继承 Person 类
1
2
3
4
5
scala复制代码public class Teacher extends Person {
private void teach(){
System.out.println("老师要讲课");
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码// 继承测试类
public class ExtendTest {
public static void main(String[] args) {
Student student = new Student();
student.setName("一颗雷布斯");
student.setAge(19);
Teacher teacher = new Teacher();
teacher.setName("张三丰");
teacher.setAge(30);

System.out.println("学生姓名:"+student.getName());
System.out.println("学生年龄:"+student.getAge());
student.eat();
System.out.println("---------------------------");
System.out.println("老师姓名:"+teacher.getName());
System.out.println("老师年龄:"+teacher.getAge());
teacher.eat();
}
}

运行结果:

3.3 继承的特点

    1. java 中的继承只支持单继承,不支持多继承。不能写成 class E extends A,B,C,D{ }。
    1. B 类继承 A 类,A 类叫做父类、超类,B 类叫做子类、派生类。
    1. 子类继承父类,除了父类的构造方法和 private 修饰的数据不能被继承外,其他的都可以被继承。
    1. C 类继承 B 类,B 类继承 A 类,C 类其实相当于间接继承了 A 类。想象一下孙子和爷爷的关系。
    1. java 中所有类默认继承 Object 类,Object 类是所有类的根类(老祖宗)。

3.4 继承使用的场景

你创建了很多类,这些类有很多相同的属性或者方法,可以用继承。

  1. 方法重写与重载

上面的例子中,学生类和教师类都继承了 Person 类,都拥有了eat( )方法,两者方法的输出结果都是“人要吃饭”。

但是老师类想要的是“老师吃饭”,学生类想要的是“学生吃饭”,有没有解决办法?

有的,方法重写。

4.1 方法重写

方法重写 (Override) 是子类对继承的父类的方法进行重新修改, 返回值和形参都不能改变,只是方法体变了。

    1. 两个类有继承关系
    1. 具有相同方法名、形参列表、返回值类型

例如:

1
2
3
4
5
java复制代码public class Student extends Person{
public void eat(){
System.out.println("学生要想学习好,必须好好吃饭!!");
}
}
1
2
3
4
5
6
7
java复制代码// 继承测试类
public class ExtendTest {
public static void main(String[] args) {
Student student = new Student();
student.eat();
}
}

运行结果:

4.2 方法重载

说了方法重写,不得不提一下方法重载。

方法重载 (overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

在同一个类当中,如果功能相似,可以使用重载。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public class Student extends Person{

public void eat(){
System.out.println("学生要想学习好,必须好好吃饭!!");
}

public void eat(String name){
System.out.println(name+"在吃饭");
}

public void eat(int age,String name){
if(age > 18){
System.out.println(name+"喜欢吃牛肉");
}else {
System.out.println(name+"喜欢吃猪肉");
}
}
}
1
2
3
4
5
6
7
8
9
java复制代码// 继承测试类
public class ExtendTest {
public static void main(String[] args) {
Student student = new Student();
student.eat();
student.eat("波吉");
student.eat(14,"卡克");
}
}

运行结果:

上面的 eat() 方法其实就用到了方法重载。

  1. super 关键字

super 字面意思是“超级的”,那必定是跟超类(父类)相关。

讲 super 之前,我们先来回忆一下 this 关键字。

    1. this 是一个关键字,是一个引用,保存了当前对象的内存地址。
    1. this 出现在实例方法中代表的是当前对象。
    1. this 不能使用在静态方法中。
    1. 当用来区分局部变量和实例变量时,this 不能省略。
    1. this 既可以出现在构造方法中,也可以出现在实例方法中。

与 this 相比,super 代表父类的引用,用于访问父类的属性、方法和构造器。

  • 1 创建子类对象,默认会先调用父类的构造方法,因为先有父,再有子。例如:
1
2
3
4
5
java复制代码public class A {
public A() {
System.out.println("父类构造方法被调用了");
}
}
1
2
3
4
5
java复制代码public class B extends A{
public B() {
System.out.println("子类构造方法被调用了");
}
}
1
2
3
4
5
java复制代码public class ExtendTest {
public static void main(String[] args) {
B b = new B();
}
}

运行结果:

    1. 使用 super. 属性 和 super.方法名() 访问父类的属性和方法,例如:
1
2
3
4
5
6
java复制代码public class A {
public String name = "哈哈哈";
public void eat(){
System.out.println("父亲要吃饭");
}
}
1
2
3
4
5
6
7
8
9
10
11
java复制代码public class B extends A{
private String name = "哦哦哦";
public void print(){
System.out.println("父亲名字:"+super.name);
System.out.println("儿子名字:"+this.name);
}
public void eat() {
System.out.println("儿子要吃饭");
super.eat();
}
}
1
2
3
4
5
6
7
java复制代码public class ExtendTest {
public static void main(String[] args) {
B b = new B();
b.print();
b.eat();
}
}

运行结果:

    1. super不能使用在静态方法中。
    1. 父类和子类中有同名属、方法,子类想访问父类,super. 不能省略
  1. Object 类

上面我们提到 Object 类是所有类的祖宗,所有的类默认都继承 Object 类。既然继承了它,那肯定是得到了他的一些属性和方法。

例如:

1
2
3
4
5
6
7
8
9
java复制代码public class ExtendTest {
public static void main(String[] args) {
Student student = new Student();
student.setName("波吉");
student.setAge(14);
System.out.println(student.toString());
System.out.println(student.getName().equals("卡克"));
}
}

运行结果:

6.1 toString()

toString() 该方法在打印对象时被调用,将对象信息变为字符串返回,默认输出对象地址。

上面的例子中输出的是一串内存地址,我们不知道什么意思,可以通过重写 Object 类的 toString() 方法来输出对象属性信息。

例如:

1
2
3
4
5
6
java复制代码public class Student extends Person{
@Override
public String toString() {
return "姓名:"+this.getName()+",年龄:"+this.getAge();
}
}
1
2
3
4
5
6
7
8
java复制代码public class ExtendTest {
public static void main(String[] args) {
Student student = new Student();
student.setName("波吉");
student.setAge(14);
System.out.println(student.toString());
}
}

运行结果:

6.2 equals()

该方法用于比较对象是否相等,例如:

1
2
3
4
5
java复制代码public static void main(String[] args) {
String name1= "波吉";
String name2= "波吉";
System.out.println(name1.equals(name2));
}

运行结果:

本文转载自: 掘金

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

MySQL 数据库之不要轻易使用枚举

发表于 2021-11-29

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

我的建议是不要去使用枚举。接下来我们就来深入的探讨下为什么会有这样的一个结论。

我们去说一说枚举的特性,枚举的类型值都是从允许值列表中去选择的。而且列表是在创建表结构的时候,就定义好的,那么表创建完成之后,我们还可以去使用 LT 语句去修改允许值列表。在存储上,这个枚举会将允许值列表变成数字索引。索引值会从 1开始。

image-20210322031343562

上图是我我绘制的一张表,它代表的是我们有一个这个枚举字段类型。那么允许值列表是 male 与famele ,它的索引就是 1 和 2。其中 NULL 和 0 是一个特殊的索引,用于标识 NULL 值和空字符串。那么这样的设计会有两点好处。第一点,存储数字数据会更加紧凑,而且节省了存储空间。第二点,允许值提前就要定义好,在存储的时候,MySQL 就可以去帮助我们检查数据的正确性。

即使是枚举类型有这样的好处,它也是存在很多弊端的。为了验证这些弊端,我们先来去创建一张数据表,用于句演示讲解。

创建数据库表 SQL 如下。

1
2
3
4
5
6
7
less复制代码CREATE TABLE `db_mysql_escape`.`t_suitable_data_type` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(32) NOT NULL,
   `gender` ENUM('male', 'female') NOT NULL,
   `grade` ENUM('0', '1', '2') NOT NULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

数据表创建好了之后,我们就可以看一看数据库表的一个数据类型以及它的结构。

1
ini复制代码desc t_suitable_data_type;

查看数据表类型以及结果如下所示。

image-20210322050330439

从上图可以看到,gender 是一个枚举类型,接受的允许值列表分别是 male 与 female,而 grade 接触的允许值列表是 0、1 和 2。除此之外就是 id 和 name 这样的两个简单属性。

我们呢先去做一个最基本的验证,插入数据需要与枚举的允许值列表相匹配,也就是刚刚所讲的枚举类型,它会让 MySQL 数据库帮助我们检查数据的正确性,我们去验证一下。

插入一条正确的数据。

1
2
go复制代码INSERT INTO `db_mysql_escape`.`t_suitable_data_type`(`id`, `name`, `gender`, `grade`) VALUES (1, 'abc', 'male', '1');
​

我们去执行一下这条插入语句。那么可以看到,这条插入语句分别往 id,name, gender,,grade 插入 1,itzmk,male,’1’,由于 male与 ‘1’ 都是定义在这个允许值列表里面的。所以说呢这条语句是可以插入成功的,也就是这条语句是合法的,此时插入成功。下图所示。

image-20210322051956276

此外,我们再去看一个不合法的语句,我们有该如何实现呢。

插入一条错误的数据。

1
go复制代码INSERT INTO `db_mysql_escape`.`t_suitable_data_type`(`id`, `name`, `gender`, `grade`) VALUES (2, 'abc', 'male', '9');

可以看到,通过上面的 SQL 知道,gender在里面插入了 male,它是合法的。但是 grade,我们设定的允许值列表是0、1 和 2。但是我们插入的字符串是 9,那么此时就会是不合法的。下图所示。

image-20210322052119215

可以看到这个数据库给我们了一个 error,插入的数据被截断 ,也就是我们所插入的数据不合法的,此时就会插入失败。

image-20210322053548253

我们可以看到只有一条这个数据,也就是我们刚刚所插入的。

这里有一个问题,能不能获取到枚举记录的索引呢?

前面讲过枚举记录,MySQL 数据库会保存的是索引。我们能不能通过 SQL 语句去查询到枚举值的索引记录呢?当然是可以的。

查看枚举值对应的索引,我们只要给字段查询的时候,去加上一个零,MySQL 数据库知道我们想要去查询的是这个枚举的索引。比如 name,gender + 0, grader,name 与 grade。查询 SQL 语句如下。

1
csharp复制代码select name, gender + 0, grade from t_suitable_data_type;

查询结果如下图所示。

image-20210322053943975

从上图中,可以看到这里的 male 字段的一个索引是1,正如我们之前所说,枚举的索引是从1 开始,那么 male 就是 1,female 就是 2。这样我们就简单的讲解了枚举的一些特性,以及怎么样去查看枚举记录的索引。

本文转载自: 掘金

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

接口测试平台演进思考

发表于 2021-11-29

很多小伙伴都比较关心如何构建一个接口自动化平台,笔者恰好有从零开始搭建自动化测试平台直到产品商业化的过程经验,可以和大家分享下。由于企业性质的问题,无法分享过多的代码,本文旨在分享个人在构建整个平台变化过程中的思考和总结,给想往这方面发展的小伙伴们一些借鉴,也算是自己的一个阶段性总结。本文主要总结了以下几个问题:

  1. 如何针对团队现状做技术型
  2. 平台演进过挰中会经历的阶段有哪些,需要分别注意什么
  3. 区分平台能力和个人能力,不要被表象迷惑

NO.1 关于技术选型的问题

万事开头难,现在世面上关于接口自动化平台的资料,不管是开源的(虽然很多都是Demo级别的),还是商用的,都非常的多。也有一些比较被业内认可的专业化工具,如Pytest,PostMan,Jmeter等等,但具体到自己的团队,应该如何选择一个好的框架做为底层基础呢?(不需要自己再造轮子,也不一定会比别人造的好)?个人认为,至少要从以下几个方面考虑。

1.1 团队的现状是什么?

如果当前团队已经有了部分接口测试的工作在开展,只是没有形成标准化、持续化,那么尽可能就以现有的工具为底层基础,进行开发,培养用户习惯的成本是很高的,别人也不一定乐意,还涉及到迁移成本的问题。如果当前团队还没进行过多的接口测试,那么就可以慎重的进行技术选型

1.2 团队的资源有哪些?

这里指的资源其实就是研发能力,会有几个人一起研发,还是你自己一个人?大家擅长的开发语言是什么?是否有能力做前端页面?是否有时间投入(很多时候并不一定会给工时)。对于开发语言而言,不需要纠结是Java还是Python,擅长哪个就用哪个。能和开发团队保持一致最好,不能也关系不大(如果你真的熟悉了一款语言,那么转换到其它语言上也不是什么难事。笔者从C切换到Java,再到现在的Python,适应过程并不会太长)

1.3 为什么要选它?

在确定完开发语言之后,就可以对应的去做选型了,各语言都有大量的开源框架可以使用(Java的JunitTest,TestNg等,Python的Pytest,HttpRunner等),本质上没有太大的区别,只要你选的框架文档齐全,还在持续更新,问题就不大。没人维护的框架不要选(特别要注意GIT上那些Demo类的框架,很容易误导人)

小结:技术问题一定要从团队的实际情况出发来实践,因为做出来的东西,不管是平台还是框架,都是要给到具体的人去落地使用的,所以要尊重你的用户,纯粹show代码能力的事少做(如果真想,去GIT上面提交代码,在公司,还是以解决问题为主)。

在确认完技术选型后,接下来就可以进入研发阶段了。此时千万不要想着要规划一个大而全的平台,而做过多技术设想,应当围绕当下团队最迫切的需求来做,按敏捷的说法,要先交付MVP版本,让团队认可你的平台,真正帮助他们解决问题。后期才会有推动别人使用的空间(这里会涉及到一个问题,那就是个人与平台的关系,很多会觉的如果平台化了,那测试人员的能力如何提升?这个问题放到最后讲,希望你能意识到这个问题,很重要)。当然,如果你有几十人的团队,那另说。

NO.2 第一阶段核心问题:能用

笔者所在的团队当时并不具备进行接口测试的能力,测试人员没有接口测试意识,且无代码能力,需要开展接口测试时,无从下手。分析当时的情况后,制定了本阶段需要解决的痛点。

**痛点1:能够通过页面配置,快速生成接口及其用例。**


**解决方案:** 基于HTTP协议(集团内部基本上都是基于此协议交互),在页面上配置相关信息,就可以生成对应的接口,并支持直接调试,避免出现接口不可用的情况。同时,对于HTTP的body内容做了丰富的支持,以便适应不同的参数类型。

image.png

痛点2:能够支持接口参数传递。

**解决方案**:HttrRunner本身就支持参数传递,也支持快速运行用例(框架的好处),所以这个痛点可以很方便的得到解决:

image.png

痛点3:产出报告对于测试人员更友好些。

解决方案: HttpRunner原生的报告比较不友好,所以自己解析原报告数据,做了聚合,让测试人员可以更直观的查看结果,同时可以让测试人员看到参数化或者数据驱动后,发出去的真实请求是什么,方便测试和开发定位问题(遇到报错的接口,如果确认是BUG,直接复制丢给开发,省事省心)。

image.png

image.png

小结:在此阶段,其实需要磨合的时间是最长的,团队的磨合、架构的磨合、业务的磨合等等,最重要的是把团队顺利的运转起来。技术上基本没什么大问题,都是基于底层框架原生的能力,做了前端的封装,降低测试人员的使用门槛,让测试人员理解、接受接口测试思想,并指导他们使用平台,设计接口测试用例,让接口测试真正落地并产生效果。

NO.3 第二阶段核心问题:好用

在完成第一阶段的内容后,进入第二阶段,这个阶段最核心的思路是推广+优化,目标是让平台好用,我们处于并将长期处于这个阶段!! 。什么是好用,用户说了算,所以团队花了比较多的时间去落地平台,去分析测试人员的痛点和难点,结合自身的经验和能力,一点点的补充平台功能。简单结总下这个阶段解决的几个典型痛点:

**痛点1:分析接口太麻烦了,需要一个个手动录。**


**解决方案:** 通过对接Swagger平台、Fiddler工具等,让测试人员不再纠结接口维护,可以更专注于用例的设计。

image.png

痛点2:部分接口开发未完成,或者一些外部接口如何处理?

解决方案:集成Mock服务,针对已经定义好的接口,可以自定义返回结果,同时生成一个只是域名不同的URL,这样在写测试用例时,想要调用Mock的接口或者真实接口时,,就只需要换个域名即可,其它都不用变,非常灵活。

image.png

痛点3:当开发的接口发生变化时,测试人员如何第一时间获知呢?

解决方案: 提供契约功能,通过算法匹配运行结果与最近一次运行成功的结果做diff,获取接口结构的变更,提醒测试人员。(现在对于Spring框架,有专门的契约测试服务可以使用,笔者当时没有注意到,所以采用了另一种思路)

image.png

痛点4:让测试执行前置,提高测试准入门槛。

解决方案: 随着公司CICD的完善,可以让开发人员在合并代码时,自动触发接口测试,验证主流程不受影响。把平台用例对接到公司的流水线上,成为质量门禁的一环,确保新代码不会影响核心功能。

image.png

痛点5:如何解决业务个性化需求?

解决方案: 随着服务的团队越来越多,我们需要对应的场景也千奇百怪,如何在标准化的平台上应对一些个性化的需求通过在接口用例中引入前后置函数的功能,让一些个性化的需求,业务团队可以自己编写部分代码去做处理,同时,这部分代码还可以保存成模板,方便复用。

image.png

image.png

还有一些如:公共登录、环境区分、数据驱动、数据生成、定时任务、文件管理 等等功能点,不再一一列举,主要还是根据团队实际需要来完善功能点,最怕的是测试开发人员自嗨,写一些测试人员不太用或者不是痛点的功能,让平台沦落成样子工程。一定是要结合测试人员的需求来开发功能点。我们处于并将长期处于这个阶段!!平台使用的技术不是业内最好的,但一定是团队当下的最优解。

小结:在此阶段,我们获得了很多团队和客户的认可,例如在集团多个BU的落地实践,协助外部客户完成测试价值提升。 平台本身也通过了信通院的DevOps三级认证(先进级),证明了平台的价值,不至于变成样子工程 ,这是笔者认为得到的最大的收获和认可。

NO.4 第三阶段:未来规划

平台功能需要不断演进,不断满足更多的需求。目前平台也没有停止探索更多的需求,在未来的规划中,我们希望解决以下问题:

问题1:测试仓库的搭建,让创建测试数据不再成为难点


问题2:接口测试与代码覆盖的对应关系,为精准测试提供数据支撑


问题3:逆向用例(混沌测试)的自动化生成


问题4:。。。。。。

NO.5 个人与平台

我们回到最初的那个话题,当我们采用平台化来做专项测试时,封装好功能,降低对测试人员的要求,只要通过页面编排就能够执行相关的测试。那么,测试人员如何提升自己呢?这里涉及到的角色有两个:个人与组织,这两个角色对平台的诉求是不一样的。

**组织:** 从组织的角度来说,希望的是有统一规划和管理,同时能够有效降低准入门槛,通过简单的培训,让更多的测试人员能够开展专项测试,所以更需要平台化的管理方式,能够提供标准化的、可视化的、操作方便的服务 。


**个人:** 当你进入到一个团队中,有现成的平台给你使用时,一定不能满足于使用平台,而是应该抓住机会去了解平台的具体实现,甚至参与到平台的研发过程中,把别人的开发设计和解决问题的思路变成自己的心得体会。如果只会依赖公司平台开展专项测试,那是平台的能力,而不是个人的能力。这也是为什么很多面试官不太喜欢大厂出来的同学,因为离开了平台,个人的能力有可能出现断崖式的下跌,切记(其实很多岗位都会有类似的问题,要学会区分哪些是自己的能力,哪些是平台给你的加成)。


 最后,感谢团队所有成员的付出和努力。团队的能力远大于个人的能力,如果能够有一群志同道合的朋友一起为同一个目标而努力,那是件很幸福的事。

原文链接

本文转载自: 掘金

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

Java应用在架构设计时该考虑什么

发表于 2021-11-29

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

前言

最近公司因业务发展需要建设一个新应用,在架构设计时我也有机会能在提供一些建议,这个过程还是比较有趣的,要找到符合业务场景和用户需求的技术,并且要考虑未来的扩展性,相对来说还是比较困难的。

涉及到后端、前端、开发规范、运维等多个方面的大量技术内容,为了以后能在类似的工作中变得更容易,我决定在本期内容中进行整理,列出一些在新项目建设时需要考虑的一些问题。

提前声明一下,本期内容在大多数问题下只提供一些解决该问题的技术,对于实现细节不是这期内容的重点;可能存在不完整的地方,主要的技术栈为Java;希望能对正在建立新系统的你有所帮助。

架构风格

首先需要明确新系统的基本架构风格,是现在比较流行的微服务架构,还是单体架构,SOA架构等。

单体架构

单体架构将所有功能包含在单个部署单元中;

可能不支持灵活的发布周期,但不需要分布式通信。

微服务

多个较小的部署单元,它们利用分布式通信来实现应用程序的功能;

支持更小、更快的版本迭代,多个团队协同开发更加灵活,但代价是分布式通信问题。

后端相关

对于要新建应用的后端,需要考虑一下这些事情。

应用服务

应用服务该如何托管?对于分布式体系结构与Spring Boot配合使用是现在很好的解决方案;

而单体结构可能更适合使用如Wildfly这样的应用服务器;

应用服务的选择通常是为要提前确定的。

系统间通信

在构建分布式系统时,因为涉及到分布式系统间的通信,而通信技术的选择也有多种,例如:通过消息中间件(例如kafka、RocketMQ等)进行异步通信,也可使用Spring MVC和Feign进行Rest通信,或者Dubbo、Spring Cloud等进行RPC通信。

接口文档

在通信方式确定之后,对于提供交互的内外部接口API需要创建某种形式的文档记录。

对于Rest API,可以使用Swagger或者使用Spring rest Docs来实现;

如果不使用接口框架,则一般使用Markdown或Asciidoctor等标记格式手动记录API;

不推荐使用Word这样极度不友好的方式记录API。

身份验证、授权

一般需要和用户交互的系统,都会涉及到用户登录、授权等功能。用户如何证明自己的身份?是否需要用户提供用户名密码,或者是通过手机号码验证等;

对于单页面的应用程序,使用OAuth认证、JWT等令牌机制实现身份验证;有些Web应用程序可能使用session+cookie就足够了;

一旦通过身份验证,应用程序还需要检查用户可以做什么操作;在服务器端,Spring Security是一个支持实现不同授权机制的框架。

数据库

如果应用程序需要结构化的数据库,则使用关系数据库如MySQL,Oracle等;
如果需要存储文档结构的数据,使用MongoDB;键值对存储使用Redis,如果有图表数据存储可以使用Neo4J。

持久层框架

在使用关系数据库时,Hibernate和MyBatis是两个常用的技术,在国内的技术选型上MyBaits更多一些;还可以使用Spring data JPA,JPA默认使用hibernate作为ORM实现,还支持许多NoSQL数据库,比如Neo4J或MongoDB。

任务调度

几乎每个大中型应用程序都需要执行计划任务,如清理数据或批量导入第三方数据等;

Spring提供了基本的作业调度功能,如Spring Batch;

对于更复杂的需求,还可以选择Quartz、Elastic Job以及xxl-job等。

日志

日志记录的技术选型基本使用SLF4J作为日志记录门面,具体日志实现使用LogBack或Log4J2。

指标监控

如果需要对于应用程序运行时的吞吐量之类的进行监控,需要使用像DropWizard Metrics、Prometheus等这样的监控框架;微服务状态,数据库连接,MQ状态、磁盘占用等指标数据也可以选择使用Spring Actuator。

前端相关

影响前端架构的因素有哪些?

前端技术体系

应用程序是否需要作为Web应用程序集中托管,还是富客户端。

如果是Web应用程序,它是客户端的单页面应用程序,还是服务器端的Web框架;

如果是富客户端,需求是支持Swing或基于JavaFX的客户端,还是支持完全不同的东西,比如Electron。

客户端数据库

是否需要在客户端存储数据,在Web应用程序中,可以使用 Local Storage 和IndexedDB;

在富客户端中,可以使用一些占用空间较小的数据库,如Derby。

外围设备

客户端是否需要访问某些类型的外围设备,如读卡器、U盾或执行某种外部测量的其他硬件;

在富客户端中,可以直接访问这些设备;

在web应用程序中,可能需要提供一个小型客户端应用程序,该应用程序可以通过本地主机上的http服务器访问这些设备并使其数据可用,该服务器可以集成到浏览器中的web应用程序中。

页面布局框架

客户端应用程序应该如何布局和设计呢,在基于HTML的客户端中,可能希望使用像Bootstrap这样的框架。对于富客户端,可用的技术可能会有很大不同。

指标监控

是否有一些事件(如错误、客户端版本等问题),需要客户端向中央服务器报告;这些事件将如何传送到服务器;

离线模式

客户端是否需要脱机工作?哪些用例应该脱机使用,哪些不应该?一旦客户端在线,客户端数据将如何与服务器同步?

运维相关

在进行系统设计时,有哪些东西是需要和运维团队进行沟通的,主要包括以下内容:

服务器

应用程序是托管在真实硬件服务器上还是托管在虚拟机上?使用Docker还是k8s等虚拟化技术。

网络通信结构

网络应该设置如何?应用程序的不同部分之间或应用程序与第三方应用程序之间是否存在网络通信障碍?

负载均衡

如何在软件的多个实例之间平衡应用程序的负载;是否有硬件负载均衡器;这个应用程序是否需要一个反向代理来将请求路由到应用程序的不同部署单元(比如使用Zuul)。

监控

如何对服务器实例的运行状况进行监控和报警?报警应该通知给谁?是否应该有一个中央仪表盘,用来测量所有类型的指标,如吞吐量等(Prometheus +Grafana是比较好的工具)。

服务注册与发现

在构建微服务架构时,可能需要服务的中央注册表,以便彼此发现。Eureka、Nacos等是现在比较流行的方案。

中央日志服务

可能需要有一个中央日志服务体系,尤其是当建立分布式服务,具有很多部署单元时,使用中央日志服务对于日志查找来说是相对容易的,不需要登录到不同的主机查找日志。

Elastic stack(Elastic 、Logstach、Kibana)是很成熟的中央日志方案。

数据库

对数据库有怎样的要求,是否需要支持数据库实例之间的热故障转移、负载平衡;师傅需要在线备份等。

开发相关

在开始开发之前,一定要与开发团队讨论这些每天都要处理的事情。

IDE

对于IDE的使用公司是否有相关政策,是否运行开发人员按自己的习惯选择;当然,我认为强制开发人员使用他/她不习惯的IDE会大大降低效率,我自己更喜欢使用IntelliJ IDEA。

构建工具

选择哪种构建工具,使用Maven还是Gradle,这两个各有优缺点。

单元测试

代码的哪些部分应该进行单元测试;使用哪些框架来实现;JUnit4和Mockito是比较好的选择。

端到端测试

代码的哪些部分应该使用自动化的端到端测试进行测试?推荐使用Selenium作为远程控制浏览器进行自动化测试的工具。

版本控制

代码如何托管,版本控制工具推荐Git,SVN已经过时了。

代码规范和质量

类和变量是如何命名的?注释如何编写?如何衡量代码质量、编码规约等;

系统的使这些指标如何统计并查看,推荐使用SonarQube这样的中央代码质量服务器。

建议选择现有的代码格式化程序和Checkstyle规则集,并将其作为构建步骤包含到构建过程中,以确保只有符

合您的约定的代码才会提交到代码库。

代码评审

开发过程中如何执行代码评审工作,怎样的周期,是开会还是使用工具。

一些版本控制工具,如GitLab支持每个用户在自己的分支上工作的工作流,直到他准备合并代码。在代码Merge之前是代码评审的一个好机会。

CI/CD(持续集成/持续部署)

构建过程将如何定期执行?是使用Jenkins服务器还是云厂商提供的工具等。

是否有自动任务可以将应用程序部署到开发、测试或生产环境,这些任务将如何执行。

日志规范

应该在何时记录什么样的日志,如何让开发人员在日志中打印有用的价值,日志应如何记录应用程序的哪些部分?什么信息?

文档

应该在类似Confluence、语雀这样的Wiki中记录文档,而不是在Word文件中;推荐使用像Markdown这样的标记格式记录文档。

小结

基于Java体系建设一个系统需要考虑的内容涉及各个方面,有很多地方可能有更好的解决方案和选择,如果你有不同的意见或者更高的工具、框架、解决方案,欢迎在下放评论区交流。

我是小黑,一个在互联网“苟且”的程序员。

流水不争先,贵在滔滔不绝


本文转载自: 掘金

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

谁有粉?就爬谁!他粉多,就爬他!Python 多线程采集 2

发表于 2021-11-29

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

今天你想爬谁的粉呢?
谁粉多,就爬谁。
那谁有粉?
沉默王二有粉。

今天咱们继续学习 Python 爬虫,从本篇博客开始进行短暂的(15 篇)多线程爬虫学习。

第一篇就要采集 @沉默王二 的粉丝,坐拥 27W+ 读者,属实让人羡慕。

目标数据源分析

本次要抓取的数据源是 https://blog.csdn.net/qing_gee?type=sub&subType=fans,其中的 ID 可以切换为你希望采集的 ID,当然包括你自己的 ID。

该页面下滑刷新会自动请求一个 API 接口,即 https://blog.csdn.net/community/home-api/v1/get-fans-list?page=3&size=20&noMore=false&blogUsername=qing_gee,其中参数如下:

  • page:页码,根据目标人粉丝总数 / 20 计算获取即可;
  • size:每页数据,默认值 20;
  • noMore:无用;
  • blogUsername:博客用户名

同时在测试接口过程中,接口会返回异常数据,实测增加一个延时控制,可以大幅度提高接口数据返回稳定性。

1
python复制代码{'code': 400, 'message': 'fail', 'data': None}

正常接口数据返回如下图所示:
谁有粉,就爬谁,@沉默王二,我要爬你的 27W+ 粉丝了

使用技术点说明

本次采用 Python 多线程实现数据的采集,编码使用 threading 模块进行多线程控制,本系列专栏从最简单的多线程开始进行学习,例如本例,一次性发起 5(可自定义)个请求。

完整代码如下所示,代码说明请参考注释部分与尾部说明

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
python复制代码import threading
from threading import Lock, Thread
import time
import os
import requests
import random


class MyThread(threading.Thread):
def __init__(self, name):
super(MyThread, self).__init__()
self.name = name

def run(self):
global urls
lock.acquire()
one_url = urls.pop()
print("正在爬取:", one_url)
lock.release()
print("任意线程等待随机时间")
time.sleep(random.randint(1,3))
res = requests.get(one_url, headers=self.get_headers(), timeout=5)

if res.json()["code"] != 400:
data = res.json()["data"]["list"]
for user in data:
name = user['username']
nickname = self.remove_character(user['nickname'])
userAvatar = user['userAvatar']
blogUrl = user['blogUrl']
blogExpert = user['blogExpert']
briefIntroduction = self.remove_character(
user['briefIntroduction'])

with open('./qing_gee_data.csv', 'a+', encoding='utf-8') as f:
print(f'{name},{nickname},{userAvatar},{blogUrl},{blogExpert},{briefIntroduction}')
f.write(f"{name},{nickname},{userAvatar},{blogUrl},{blogExpert},{briefIntroduction}\n")
else:
print(res.json())
print("异常数据", one_url)
with open('./error.txt', 'a+', encoding='utf-8') as f:
f.write(one_url+"\n")
# 去除特殊字符

def remove_character(self, origin_str):
if origin_str is None:
return
origin_str = origin_str.replace('\n', '')
origin_str = origin_str.replace(',', ',')
return origin_str
# 获取随机UA请求头
def get_headers(self):
uas = [
"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
]
ua = random.choice(uas)
# 特别注意下述 cookie 部分,需要手动从开发者工具中进行复制,否则抓取到的数据,缺少nikename 与个人简介部分
headers = {
"user-agent": ua,
'cookie': 'UserName=你的ID; UserInfo=你的UserInfo; UserToken=你的UserToken;',
"referer": "https://blog.csdn.net/qing_gee?type=sub&subType=fans"
}
return headers


if __name__ == '__main__':
lock = Lock()
url_format = 'https://blog.csdn.net/community/home-api/v1/get-fans-list?page={}&size=20&noMore=false&blogUsername=qing_gee'
urls = [url_format.format(i) for i in range(1, 13300)]
l = []
while len(urls) > 0:
print(len(urls))
for i in range(5):
p = MyThread("t"+str(i))
l.append(p)
p.start()
for p in l:
p.join()

代码运行结果如下图所示:
谁有粉,就爬谁,@沉默王二,我要爬你的 27W+ 粉丝了

上述代码用到了多线程,也同时用到了线程锁,简单的多线程代码可以抽象为下述内容。

简单的多线程代码:

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码import threading
import time

def run(n):
print('task', n)
time.sleep(3)

if __name__ == '__main__':
t1 = threading.Thread(target=run, args=('t1',))
t2 = threading.Thread(target=run, args=('t2',))
t1.start()
t2.start()

其中比较核心的代码是 threading.Thread,参数 target 后面的值是函数名,args 是传递的参数,注意必须为元组类型。

爬虫代码还是用了共享全局变量,简化代码如下所示,其中重点学习 lock=Lock() 部分代码,以及在使用全局变量前后的 lock.acquire() 和 lock.release()。其中还用到了线程的 join 方法,该方法主要是为了让主线程等待子线程执行。

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
python复制代码import threading
from threading import Lock,Thread
import time,os

def work():
global urls
lock.acquire()
# 获取一个 url
one_url = urls.pop()
lock.release()

print("得到的 URL 为",one_url)


if __name__ == '__main__':
lock = Lock()
url_format = 'https://blog.csdn.net/community/home-api/v1/get-fans-list?page={}&size=20&noMore=false&blogUsername=qing_gee'
# 拼接URL,全局共享变量
urls = [url_format.format(i) for i in range(1, 13300)]
l = []
# 开启线程数量
for i in range(3):
p = Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()

拿到这些数据,可以针对性的去描述一个作者的用户画像了,本部分在后续的博客中为大家单独开一篇详细介绍。

代码在数据清理部分,还有优化的空间,由于设置了 13300 页数据,故最终抓取到 26W+数据,查询了一下,存在 梦想橡皮擦。

谁有粉?就爬谁!他粉多,就爬他!Python 多线程采集 27W+ 粉丝数据

关注者中至少有 83 位博客专家,可以看到博客专家的个人简介写的都比较清楚,同时发现 jiangtao(CSDN 创始人)

谁有粉?就爬谁!他粉多,就爬他!Python 多线程采集 27W+ 粉丝数据

收藏时间

代码下载地址:codechina.csdn.net/hihell/pyth…,可否给个 Star。

来都来了,不发个评论,点个赞,收个藏吗?

本文转载自: 掘金

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

1…119120121…956

开发者博客

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