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

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


  • 首页

  • 归档

  • 搜索

快来揭秘网易年度报告的制作过程 效果图 准备 代码和分析 最

发表于 2024-04-23

效果图

image.png

image.png

准备

close.png

close.png

music.png

music.png

自行找一个音频,文件摆放位置如下

image.png

代码和分析

html

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
xml复制代码<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>年度听歌报告</title>
<link rel="icon" href="https://s1.music.126.net/style/favicon.ico">
<link rel="stylesheet" href="./index.css">
</head>
<body>
<audio src="./assets/bgm.mp3" id="j-bgm"></audio>
<div class="music-btn off"></div>

<div class="view">
<div class="sun"></div>
<!-- 秋千 -->
<div class="art">
<div class="swing">
<div class="leg2">
<div class="jiojio"></div>
</div>

<!-- neck -->
<div class="neck"></div>
<!-- head -->
<div class="head">
<div class="part"></div>
</div>

<div class="leg1">
<div class="jiojio"></div>
</div>
</div>
</div>
<!-- 特殊内容 -->
<div class="paras">
<p class="para f-animLineUp" style="transition-delay: 0.2s;">
<em class="s-fcRed">11月11日</em>
</p>
<p class="para f-animLineUp" style="transition-delay: 0.3s;">
大概是很特别的一天
</p>
<p class="para f-animLineUp" style="transition-delay: 0.4s;">
这一天里
</p>
<p class="para f-animLineUp" style="transition-delay: 0.5s;">
你把郑源的
<em class="s-fcRed">《被伤过的心还可以爱谁》</em>
</p>
<p class="para f-animLineUp" style="transition-delay: 0.6s;">
反复听了
<em class="s-fcRed">10次</em>
</p>
</div>
</div>
</html>

分析

  • 定义一个audio标签元素用于存放音频
  • 定义一个类名为music-btn off用于存放音乐开关
  • 定义一个类名为view的容器:
  1. 在该容器内定义sun容器,用于存放背景中的太阳
  2. 在该容器内定义art容器,在art容器中再定义swing容器,用于存放秋千和人,在swing容器中再定义leg2,neck,head,leg1分别用于存放腿2,脖子,头,腿1
  3. 在leg2和leg1再定义jiojio容器,用于存放脚
  4. 在head容器中再定义part容器,用于存放头发
  • 定义paras容器,用于存放文字,里面通过p和em标签元素来实现效果,设置style=”transition-delay: xxxs;” 来实现文字慢慢出现的效果
  • css*
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
css复制代码*{
margin: 0;
padding: 0;
}
html, body{
width: 100%;
height: 100%;
}
.music-btn{
position: fixed;
top: 25px;
left: 25px;
width: 40px;
height: 40px;
background: url(./assets/close.png);
background-size: cover;
z-index: 3;
}
.music-btn.off{
background-image: url(./assets/music.png);
}
.view{
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
background-image:linear-gradient(60deg,#f8ddd1,#faece5 73%,#fad2c0) ;
}
.sun{
position: absolute;
top: 45%;
left: 50%;
width: 283px;
height: 283px;
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/sun.a3f575ae2fef2cfdae15011e6081a094.png);
background-size: cover;
transform: translate(-50%,-50%);
}
.art{
position: absolute;
top: -140px;
right: 0;
width: 750px;
height: 1334px;
transform: scale(0.5);
transform-origin: top right;
}
.swing{
position: absolute;
left: 226px;
top: -180px;
width: 478px;
height: 1038px;
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/swing.88545d6c8e1ac798465e367f8e5357ab.png);
transform-origin: -16% -30%;
animation: ani_qiuqian 6s cubic-bezier(0.455,0.03,0.515,0.955) infinite;
}

@keyframes ani_qiuqian{
0% {
transform: rotateZ(0deg);
}
50% {
transform: rotateZ(32deg);
}
100% {
transform: rotateZ(0deg);
}
}

.leg2{
position: absolute;
left: 185.375px;
top: 958px;
width: 130px;
height: 32px;
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/leg2.d7bc44a91b6974450f2ccc430846c63d.png);
transform-origin: 91.15% 33.59%;
animation: ani_leg2 8s ease infinite;
}

@keyframes ani_leg2{
0% {
transform: rotateZ(0deg);
}
25% {
transform: rotateZ(-87deg);
}
50% {
transform: rotateZ(0deg);
}
75% {
transform: rotateZ(-87deg);
}
100% {
transform: rotateZ(0deg);
}
}

.leg2 .jiojio{
position: absolute;
width: 57px;
height: 44px;
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/leg2-part.8f70bb7fc789a70bc78c48aa7718a765.png);
left: -27.75px;
top: -10.5px;
}

.leg1{
position: absolute;
left: 290.375px;
top: 955.25px;
width: 63px;
height: 130px;
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/leg1.b1df6a7d1a794d36fbd0e1277733e1cf.png);
transform-origin: 17.857% 13.365%;
animation: ani_leg1 8s ease infinite;
}

@keyframes ani_leg1{
0% {
transform: rotateZ(0deg);
}
25% {
transform: rotateZ(109deg);
}
50% {
transform: rotateZ(0deg);
}
75% {
transform:rotateZ(109deg);
}
100% {
transform: rotateZ(0deg);
}
}
.leg1 .jiojio{
position: absolute;
width: 39px;
height: 62px;
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/leg1-part.f2f17703a6af8fd2af5e0f5a9f320623.png);
left: 26.25px;
top: 102.5px;
}
.swing .neck {
position: absolute;
left: 451.125px;
top: 855.5px;
width: 51px;
height: 42px;
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/neck.07a0013beff9796ed79c2cea542e5af2.png) no-repeat;
}
/* 头 */
.swing .neck, .swing .head {
display: block;
position: absolute;
left: 451.125px;
top: 855.5px;
width: 51px;
height: 42px;
}
/* 脖子 */
.swing .neck {
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/neck.07a0013beff9796ed79c2cea542e5af2.png) no-repeat;
}
/* 头 */
.swing .head {
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/head.90bf892023d7df0522a4b53fc07e38df.png) no-repeat;
animation: ani2_head 8s ease infinite;
}
/* 头发 */
.swing .head .part {
background: url(https://s5.music.126.net/static_public/5c21db8d4684556c72180904/head-part.22d4381c4bd6cb1c3afd2b1bfcfe22f1.png) no-repeat;
left: 20px;
top: 2px;
width: 40px;
height: 47px;
position: absolute;
}
@keyframes ani2_head {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(-55deg);
}
62.5% {
transform: rotate(-55deg);
}
87.92% {
transform: rotate(0deg);
}
100% {
transform: rotate(0deg);
}
}
/* 特殊的内容 */
.paras {
bottom: 110px;
left: 10.67%;
position: absolute;
line-height: 1.6667;
letter-spacing: 1px;
color: #333;
}
.s-fcRed {
color: #df493a;
}
.z-enter .f-animLineUp {
opacity: 1;
transform: translateY(0);
transition: opacity 1.2s,transform 1s;
}
.f-animLineUp {
opacity: 0;
transform: translateY(6px);
}
em, i {
font-style: normal;
text-align: left;
}

分析

  • 秋千动画部分实现是通过@keyframes来实现,设置分别在0%,50%,100%时绕Z轴旋转的角度:
1
2
3
4
5
6
7
8
9
10
11
12
css复制代码
@keyframes ani_qiuqian{
0% {
transform: rotateZ(0deg);
}
50% {
transform: rotateZ(32deg);
}
100% {
transform: rotateZ(0deg);
}
}

然后在秋千swing的css中通过animation来实现:

1
css复制代码    animation: ani_qiuqian 6s cubic-bezier(0.455,0.03,0.515,0.955) infinite;
  • leg2和leg1,head也是同样的操作
  • js*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ini复制代码 //播放音乐
var viewSpecial = document.querySelector('.view .paras');
var musicBtn = document.querySelector('.music-btn');
var bgMusic = document.getElementById('j-bgm');
var defaultMusicPlay = true;

musicBtn.addEventListener('click' , function() {
if ( defaultMusicPlay ){
bgMusic.play();
// musicBtn.classList.remove('off');
} else {
bgMusic.pause();
// musicBtn.classList.add('off');
}
musicBtn.classList.toggle('off');//如果有自动移除;如果没有自动添加

defaultMusicPlay = !defaultMusicPlay;
})
setTimeout(() => {
viewSpecial.classList.add('z-enter')
}, 1000)

分析

  • 通过document.querySelector(‘xxx’) 来获取viewSpecial(文字容器),musicBtn容器(音乐按钮容器)
  • 通过document.getElementById(‘xxx’) 来获取bgMusic容器(音乐容器)
  • 定义defaultMusicPlay = true来实现音乐开关和图标切换
  • 给 musicBtn绑定一个监听事件addEventListener,当点击的时候触发回调函数,如果defaultMusicPlay为true则音乐播放并且显示close.png,如果为false,则关闭音乐并显示music.png,以下代码可以实现音乐的开关和图片的切换
1
ini复制代码defaultMusicPlay = !defaultMusicPlay;
  • 下面代码可以实现如果有’off’自动移除;如果没有自动添加
1
arduino复制代码musicBtn.classList.toggle('off');

最后

如果有收获的话,可以给小编点个赞鼓励鼓励嘛,有错误的话希望可以提出,我一定虚心请教,继续努力,带来更优质的文章!

image.png

本文转载自: 掘金

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

flutter Android端谷歌登录(不使用fireba

发表于 2024-04-23

flutter开发主要引入插件google_sign_in | Flutter package (pub.dev)。

由于google_sign_in文档中描述不清楚,本想通过查阅其他人的源码以加快自己的上手速度,从而忽略flutter/android开发者文档的重要性。

前期入手文档,介绍流程挺详细,也适合小白专看。详见下文:
Google Sign-in in Flutter (without Firebase) | by danial | Medium (danials.space)

但是,该文中却有几处误导了我。

第一个误导处,见下图,按理解应该是从AndroidManifest.xml中获取package内容并填入Package Name中。我也确实是如此做了。

image.png

第二个误导处,见下图,GoogleSignIn的用法,需要填入ServerClientId或者ClientId。

image.png

如果按照上两处流程走,那不可避免到获取用户信息环节出现“APIException:10 null null”。为了解决这个问题,查看了各种各样的文档,也发现有很多提问者还在继续提问着。我按照各个网上的解答,尝试了几次都无功而返。但是,就是没有想起去查阅flutter官方文档……

幸好功夫不负有心人,最终被我找到一篇文章。详见链接下文:Flutter Google Sign In Without Firebase - Sudorealm

这篇文章中提到了几点,让我无地自容。

  1. Package Name 的理解以及配置错误。

image.png

  1. 在app/build.gradle配置中看到了明显的提示。框架自身专门明显提示:TODO内容。

image.png

根据上图中的链接去查阅,就联想到这三者间的关系。

  1. 代码介绍中并没有使用到serverClientId和clientId。

image.png

综上所述几点修正后,成功获取到google user信息。

本文转载自: 掘金

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

Flutter开发 -- 条件绘制

发表于 2024-04-23

先水一篇,后面扩种这篇的内容。当然就不止是条件绘制了。

很多时候难免会出现一种情况,那就是屏幕显示的一个或者多个Widget和某些条件相关联。满足条件的显示出来,不满足的不显示。这就需要用到条件绘制。

用最常见的三元操作符 ?:

这个是最常见的,在Flutter是,在隔壁React也是。使用的非常普遍。

上面的代码是说,如果满足条件controller.hasSuchField(‘due date’),就显示一个Icon,否则什么都不显示。

或者

使用If-case或者switch表达式

if-case:

Switch表达式

使用一个匿名方法

根据value的值返回带有不同文本的Text。

用Builder

这是Flutter提供的实现复杂的绘制的工具。万能的builder在各种第三方的状态管理库里频繁出现。

现在看一个例子:

使用自定义方法


这个方式在Flutter里也会经常用到。不管是高情商代码抽象,还是低情商减少缩进。

以上大部分都可以用在child属性下。但是在Row,Column这样的children属性不一定适用。现在看看对children怎么实现条件绘制:

image.png

其实也是用了if表达式,或者类似于三元操作符。只是这里多了一个对Widget列表的操作。就是那个spread operator。让widget列表spread在children里。

本文转载自: 掘金

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

如何在MacOS下安装Flutter开发环境 如何在MacO

发表于 2024-04-23

如何在MacOS下安装Flutter开发环境

通过Google推出的Flutter framework,我们可以更为方便的开发跨平台APP,开发出来的APP能够在iOS、Android、Web、macOS、Windows和Linux上都可以执行。

在开发 Flutter App 之前,我们必须要先准备好开发环境,下面我将介绍如何在Mac环境下从零开始Flutter App的开发环境。

  • 安装Xcode;
  • 安装Android Studio;
  • 安装VS Code;
  • 安装CocoaPods;

安装Xcode

Xcode的安装比较简单,在Apple Store当中搜索Xcode即可。

第一次执行Xcode需要进行一些配置,我们可以在命令行当中输入如下几条命令:

1
2
bash复制代码sudo sh -c 'xcode-select -s /Applications/Xcode.app/Contents/Developer && xcodebuild -runFirstLaunch'
sudo xcodebuild -license

安装Android Studio

安装有以下几种途径可以安装:

  1. 官网下载: developer.android.com/codelabs/ba…;
  2. 通过Jetbrains的Jetbrains Toolbox来进行安装。
  3. brew install --cask android-studio

安装VS Code

  1. brew install --cask visual-studio-code
  2. 官网下载:code.visualstudio.com/download

安装Flutter SDK

1
bash复制代码brew install --cask flutter

它的路径在:

1
bash复制代码/opt/homebrew/Caskroom/flutter/{版本号}/flutter

安装好之后,需要执行一次flutter:

1
bash复制代码flutter

它会自动初始化下载安装Dart SDK和一些构建工具等,这些文件都在Flutter SDK下的bin/cache路径下。

现在,我们查看Flutter的版本号:

1
bash复制代码flutter --version

它将会输出大概下面这样的信息:

1
2
3
4
bash复制代码Flutter 3.19.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 54e66469a9 (6 days ago) • 2024-04-17 13:08:03 -0700
Engine • revision c4cd48e186
Tools • Dart 3.3.4 • DevTools 2.31.1

这就代表着Flutter SDK安装成功了。

我使用的主力IDE是Android Studio,当Flutter SDK安装好之后,我们需要在:

Android Studio -> Preference -> Languages & Frameworks -> Flutter

填入Flutter SDK安装的路径即可初始化Flutter的IDE环境。

安装CocoaPods

1
bash复制代码brew install cocoapods

测试开发环境

Flutter的CLI提供了一个参数用于测试开发环境的完整度:

1
bash复制代码flutter docker

出现Xcode installation is incomplete错误

执行以下两条命令既可以解决问题:

1
2
bash复制代码sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch

A network error occurred

国内需要$HOME/.bash_profile或者$HOME/.zshrc里面添加:

1
2
bash复制代码export PUB_HOSTED_URL=https://pub.flutter-io.cn # 国内用户需要设置
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn # 国内用户需要设置

参考资料

  • Start building Flutter iOS apps on macOS
  • 在 Mac 上準備 Flutter App 的開發環境

本文转载自: 掘金

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

完了,安卓项目代码被误删了 写在前面 逆向 代码

发表于 2024-04-23

写在前面

这是一个朋友的经历记录了下来。朋友开发完了一个公司的app,过了一段时间,在清理电脑空间的时候把该app的项目目录给删了,突然公司针对该app提出了新的需求,这不完了?幸好有之前打包好的apk,所以可以通过逆向去弥补…..

Apk文件结构

apk的本质是压缩包,apk解压后会生成下列所示文件夹

  • Assets:存放的是不会被编译处理的文件。
  • Lib:存放的是一些so库,native库文件
  • META-INF:存放的是签名信息,用来保证apk的完整性和系统安全。防止被重新修改打包。
  • res:存放的资源文件,图片、字符串、颜色信息等
  • AndroidManifest.xml:是Android程序的配置文件,权限和配置信息
  • Classes.dex:Android平台下的字节码文件。
  • Resources.arcs:编译后的二进制资源文件,用来记录资源文件和资源ID的关系

逆向

这里用了逆向神器——jdax。支持命令行和图形化界面,地址如下:

github.com/skylot/jadx…

下载好之后,直接解压后打开exe,将apk文件拖入进去就可以,图形化界面,更方便搜索查看,可以看到下列文件夹

先看资源文件,asset存放的是静态资源文件,一般不会被压缩,但是会占用更多的安装包空间,res文件是由Android目录下的res进行压缩得到的,所以里面的文件直接解压打开会乱码,在这个工具里打开是正常的。

话不多说直接找回我的代码,找到我写的一个类,拷贝回去,补齐里面缺失的资源文件和一些新增的接口,跟着自己之前开发的流程,一步一步的找回去,发现其中局部变量在编译的时候都被进行了优化,以便缩小体积

找到我写的最核心的代码,发现被混淆了,我在代码里没有进行代码混淆配置,还是被一些工具给我进行了混淆,只能凭借着记忆去还原了。

终于进行了不到一天多的时间,把所有的代码还原了,然后自测通过。

代码混淆

现在其实也可以看到自己的程序是非常危险的,任何人拿到我的apk进行一个逆向就可以看到大概的逻辑。所以要在Android中进行代码混淆的配置。

项目中如果含有多个module时,在主app中设置了混淆其他module都会混淆,在build.gradle中配置下列代码 proguardFiles getDefaultProguardFile

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码android {
...
buildTypes {
release {
minifyEnabled true // 开启代码混淆
zipAlignEnabled true // 开启Zip压缩优化
shrinkResources true // 移除未被使用的资源
//混淆文件列表,混淆规则配置
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
...
}

这里代表的是混淆文件,我们在项目里找到proguard-rules.pro,这里就是混淆规则,规定了哪些代码进行混淆,哪些不进行混淆。混淆规则一般有以下几点:

  • 混淆规则,等级、预校验、混淆算法等
  • 第三方库
  • 自定义类、控件
  • 本地的R类
  • 泛型 注解 枚举类等

示例配置如下:

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
ruby复制代码
#压缩等级,一般选择中间级别5
-optimizationpasses 5
#包名不混合大小写
-dontusemixedcaseclassnames
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
#优化 不优化输入的类文件
-dontoptimize
#预校验
-dontpreverify
#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#保护注解
-keepattributes *Annotation*
#保持下面的类不被混淆(没有用到的可以删除掉,比如没有用到service则可以把service行删除)
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.preference.Preference
-keep public class * extends android.support.v4.app.FragmentActivity
-keep public class * extends android.support.** { *;}
#如果引用了v4或者v7包
-dontwarn android.support.*
#忽略警告(开始应该注释掉,让他报错误解决,最后再打开,警告要尽量少)
-ignorewarnings
#####################记录生成的日志数据,gradle build时在本项目根目录输出################
#混淆时是否记录日志
-verbose
#apk 包内所有class 的内部结构
-dump class_files.txt
#为混淆的类和成员
-printseeds seeds.txt
#列粗从 apk 中删除的代码
-printusage unused.txt
#混淆前后的映射
-printmapping mapping.txt
#####################记录生成的日志数据,gradle build时在本项目根目录输出结束################

#本地的R类不要被混淆,不然就找不到相应的资源
-keep class **.R$*{ public static final int *; }

#保持内部类,异常类
-keepattributes Exceptions, InnerClasses
#保持泛型、注解、源代码之类的不被混淆
-keepattributes Signature, Deprecated, SourceFile
-keepattributes LineNumberTable, *Annotation*, EnclosingMethod

#保持自定义控件不被混淆(没有就不需要)
-keepclasseswithmembers class * extends android.app.Activity{
public void *(android.view.View);
}
-keepclasseswithmembers class * extends android.supprot.v4.app.Fragment{
public void *(android.view.View);
}
#保持 Parcelable 不被混淆(没有就不需要)
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#保持 Serializable 不被混淆(没有就不需要)
-keepnames class * implements java.io.Serializable

-keepclassmembers class * {
public void *ButtonClicked(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

再次打包,然后打开apk后就会发现包名类名变量名都变得很奇怪。

这样代码混淆就完成了。

本文转载自: 掘金

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

Atlas Vector Search:借助语义搜索和 AI

发表于 2024-04-23

img_v3_02a3_946492f5-4e42-4cb3-a5be-41858b30ea6g.jpg

[Atlas Vector Search已正式上线!](Vector Search 和专用 Search Nodes:现已正式发布 (qq.com))

Vector Search(向量搜索)现在支持生产工作负载,开发者可以继续构建由语义搜索和生成式人工智能驱动的智能应用,同时通过 Search Node(搜索节点)优化资源消耗并提高性能。

这一刻终于到来:人工智能已触手可及。曾经,数据科学与机器学习是高深莫测的领域,仅为企业内部的专业人士所掌握;然而如今,这些技术的神秘面纱已被揭开,现已向世界各地的创造者敞开了大门。

但若想深入挖掘这些新兴工具的巨大潜能,开发者需要一个可信赖、可灵活组合、精巧高效的数据平台作为基础。同时,这些新能力的效果好坏,取决于它们能够获取的数据或“基本事实”的质量。

因此,我们为 MongoDB Atlas 开发者数据平台增加了一项新的功能,让开发者的数据释放出无限可能,助力 AI 应用的发展——MongoDB 隆重推出全新的 Vector Search 功能,它能够适应各种形式的数据需求,让我们的合作伙伴享受这些惊人新功能带来的好处。

向量搜索的原理和优势

Vector Search 是一种基于语义或数据含义,而不是基于数据本身来查询数据的功能。这种功能的实现原理是,把任何形式的数据转换成数字向量,再用高级算法进行相互比较。

第一步是获取源数据,可以是文本、音频、图像或视频数据源,并使用“编码模型”将其转换为“向量”或“嵌入”。得益于人工智能的最新进展,这些向量现在能够将低维数据投影到包含更多数据上下文的高维空间,从而更准确地理解数据的含义。

一旦数据转换成数字表示,就可以使用“近似最近邻”算法查找相似的值,这种算法可以让查询非常快速地找到具有相似向量的数据。用户可以使用自然语言进行查询,例如:“推荐一些悲伤的电影”,或“找一些类似……的图片”。这项功能解锁了全新的可能性。

点击观看这两支视频,帮助你更好地了解Vector Search:

【向量搜索:数据查询的未来|语义搜索】

[【3分钟了解向量搜索】](3分钟了解MongoDB Atlas向量搜索_哔哩哔哩_bilibili)

MongoDB Atlas平台已原生内置向量搜索!

MongoDB Atlas 已原生内置此功能,开发者无需复制和转换数据、无需学习新的技术栈和语法,也无需管理一整套新的基础设施。 借助 MongoDB 的 Atlas Vector Search,开发者可以在一个经过实战考验的出色平台中利用这些强大的新功能,以前所未有的速度构建应用。

有效使用 AI 和 Vector Search 所面临的许多挑战,都源于保证应用数据安全所涉及到的复杂性。这些繁琐的任务会降低开发效率,并让应用的构建、调试和维护变得更加困难。MongoDB 消除了所有这些挑战,同时将 Vector Search 的强大能力整合到平台中,无论面对什么样的工作负载,该平台都能灵活地纵向和横向扩展,轻松应对。

最后,如果没有安全性和可用性的保证,这一切都毫无意义。MongoDB 致力于提供安全的数据管理解决方案,通过冗余和自动故障转移保证高可用性,让应用始终稳定运行。

MongoDB.local 伦敦见面会发布的新功能

在 .Local 伦敦见面会上,我们很高兴地宣布推出专门的Vector Search 聚合阶段,它可以通过 $vectorSearch 调用。这个新的聚合阶段引入了一些新概念,增加了新的能力,使得 Vector Search 比以往任何时候都更容易使用。

借助 vectorSearch,开发者还可以通过MQL语法使用∗∗预过滤器∗∗(如vectorSearch,开发者还可以通过 MQL 语法使用**预过滤器**(如 vectorSearch,开发者还可以通过MQL语法使用∗∗预过滤器∗∗(如gte、$eq 等),以在遍历索引时过滤掉一些文档,从而获得一致的结果和更高性能。任何了解 MongoDB 的开发者都能够轻松使用此过滤功能!

最后,我们还介绍了在聚合阶段内部调整结果的两种方法,即 “numCandidates”和“limit”参数。通过这些参数,开发者可以调整应该成为近似最近邻搜索候选者的文档数量,然后通过“limit”限制结果数量。

它如何与生态系统交互?

人工智能的发展日新月异,让人叹为观止,而开源社区的突飞猛进也令人赞叹不已。开源语言模型以及将它们集成到应用中的各种方法取得了巨大的进步。人工智能展现出了强大力量,因此,建立一个能够让开发者自由发挥的坚实抽象也变得前所未有地重要。基于这样的考虑,我们非常激动地告诉大家,LangChain 和 LlamaIndex 支持我们的多种功能,包括 Vector Search、聊天日志 (Chat Logging) 和文档索引等。我们正在快速推进,并将继续为主要提供商发布新功能。

总结

一切才刚刚开始,MongoDB 致力于提供优秀的开发者数据平台,助力开发者打造新一代 AI 赋能的应用。我们还会不断研究和支持更多的框架和插件架构。但始终不变的是,这一切的核心都是开发者。我们将与社区交流,找到最合适的服务方式,让开发者在每一步都感到满意。放手去创造吧!

点击【阅读原文】访问中文官网Vector Search产品页,并获取教程和更多AI资源。

MongoDB Atlas

MongoDB Atlas 是 MongoDB 公司提供的 MongoDB 云服务,由 MongoDB 数据库的开发团队构建和运维,可以在AWS、Microsoft Azure、Google Cloud Platform 云平台上轻松部署、运营和扩展。MongoDB Atlas 内建了 MongoDB 安全和运维最佳实践,可自动完成基础设施的部署、数据库的构建、高可用部署、数据的全球分发、备份等即费时又需要大量经验运维工作。让您通过简单的界面和 API 就可以完成这些工作,由此您可以将更多宝贵的时间花在构建您的应用上。


👉点击访问 MongoDB中文官网

👉立即免费试用 MongoDB Atlas

☎️需要支持?欢迎联系我们:400-8662988

✅欢迎关注MongoDB微信订阅号(MongoDB-China),及时获取最新资讯。

本文转载自: 掘金

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

Python 中下划线的 5 种含义

发表于 2024-04-23

左手编程,右手年华。大家好,我是一点,关注我,带你走入编程的世界。

公众号:一点sir,关注Python领取编程资料

在Python中,下划线是一个多功能的字符,它在不同的上下文中扮演着不同的角色。Python社区通过使用下划线来约定一些特定的行为和模式,这些约定有助于提高代码的可读性和一致性。

单下划线开头(保护成员)

当一个变量或方法名称以单个下划线开头时,它被视为“受保护的”(protected),这是一种约定,表明这个成员不应该被公开访问,而是作为类的内部实现细节。

1
2
3
class MyClass:
def __init__(self):
self._internal_var = 10 # 受保护的成员变量

双下划线开头(名称改写)

如果一个成员名称以双下划线开头,Python将使用名称改写(也称为“命名冲突解决”)来解决可能的冲突。这会导致一个不同的名称被创建,通常是通过在原始名称前添加两个下划线和类名的方式。

1
2
3
4
5
6
7
8
9
10
11
class Parent:
def __method(self):
pass

class Child(Parent):
def __method(self):
pass

c = Child()
c._P_method() # 正确,调用Parent的__method
c.__method() # 错误,Python不允许直接访问

单下划线结尾(避免命名冲突)

当一个变量名以单个下划线结尾时,这通常用来避免与Python的关键字发生冲突。

1
2
3
class MyClass:
def __init__(self):
self.class_ = "Python" # 避免与关键字class冲突

双下划线特殊方法

以双下划线开头和结尾的名称在Python中用于特殊方法,也称为魔术方法(如__init__, __str__, __call__等)。这些方法有特定的用途,并且当以特定方式调用时,Python解释器会自动使用它们。

1
2
3
4
5
6
7
8
9
class MyClass:
def __init__(self):
self.value = 1

def __call__(self):
print("Called as a function!")

obj = MyClass()
obj() # 调用__call__方法,输出: Called as a function!

下划线开头的变量(从导入中忽略)

在模块级别,如果一个变量或函数名称以下划线开头,它们默认不会被from module import *语句导入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# some_module.py
def _private_function():
print("This won't be imported with *")

def public_function():
print("This will be imported with *")

_private_var = "This won't be imported with *"
public_var = "This will be imported with *"

# other_script.py
from some_module import *

print(public_function()) # 正常工作
print(_private_function()) # 错误,_private_function未被导入

下划线在Python中的使用是社区约定的一部分,它帮助开发者理解特定变量或方法的预期用途。正确地使用下划线可以减少命名冲突,提高代码的清晰度,并且遵循Python的编码风格。

本文转载自: 掘金

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

手把手教你从零搭建一款属于自己的dApp全栈项目(Vue3+

发表于 2024-04-23

前言

去中心化应用(dApp)是构建在区块链技术上的应用程序,具有去中心化、透明、安全的特性。dApp 开发需要掌握区块链技术、智能合约编写、前端和后端开发等多个领域。

如果不懂区块链,不会智能合约开发、也不会前后端。别担心,看此文就对了,按照本教程步骤操作可以快速搭建属于你的 dApp 项目。

学习路线图

w1.png

效果截图

image.png

image.png

项目结构

image.png

技术栈

  • Vue3:是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
  • Typescript:是由微软进行开发和维护的一种开源的编程语言。TypeScript 是 JavaScript 的严格语法超集,提供了可选的静态类型检查。
  • Ethers.js v6:是一个强大的 JavaScript 库,用于与以太坊区块链进行交互。 它提供了丰富的 API,使得开发者能够轻松地发送交易、读取数据、与智能合约交互等。
  • Hardhat:是一个用于开发以太坊智能合约和 dApp 的开发框架和工具套件。提供了一套功能强大的工具,用于编译、部署、测试和调试智能合约,并与以太坊测试网络进行交互。
  • Ganache:是一个以太坊的个人开发环境,你可以在上面部署合约、开发程序和进行测试。它有桌面版本和命令行工具版本,同时提供对 windows 、Mac 和 Linux 的支持。
  • Solidity:是一种高级的、面向合约的编程语言,用于在以太坊虚拟机 (EVM) 上构建智能合约。它受到 C++、Python 和 JavaScript 的影响,并与这些语言共享语法和编程概念。
  • Metamask(小狐狸):是用于与以太坊区块链进行交互的软件加密货币钱包。它可以通过浏览器扩展程序或移动应用程序让用户访问其以太坊钱包,与去中心化应用进行交互。
  • Element Plus:是一个基于 Vue 3 的高质量 UI 组件库。它包含了丰富的组件和扩展功能,例如表格、表单、按钮、导航、通知等,让开发者能够快速构建高质量的 Web 应用。
  • Remix IDE:是一款可以在线快速编写、调试和部署合约代码的编辑器,完全在浏览器环境中运行,方便易用,非常适合智能合约开发初学者使用。

功能模块

  • 钱包登录
  • 查询余额
  • 我要提款
  • 发布消息
  • 消息列表

准备工作

  • NodeJS 版本v18+
  • VSCode 代码编辑器
  • 安装Metamask浏览器钱包插件(需要科学上网):metamask.io/download/

环境搭建

构建项目

1
bash复制代码yarn create vite vue3-dapp-demo

image.png

1
2
3
bash复制代码cd vue3-dapp-demo
yarn
yarn dev

image.png

image.png

初始化合约

在根目录新建文件夹my_contact,作为合约项目文件夹。安装hardhat依赖需进入此文件夹后运行如下命令:

1
2
bash复制代码cd my_contact
yarn add -D hardhat

image.png

安装成功后,接下来创建简单的hardhat项目,在之前创建好的my_contact文件夹中运行如下命令:

1
bash复制代码npx hardhat init

让我们创建 JavaScript 或 TypeScript 项目,并完成这些步骤来编译、测试和部署示例合约。我们建议使用 TypeScript,但如果您不熟悉它,只需选择 JavaScript。

image.png

初始化hardhat项目完成后,再运行npx hardhat & npx hardhat help命令,会看到相关帮助信息如下图所示:

image.png

  • contracts:存放智能合约文件的目录
  • ignition:存放部署智能合约文件的目录
  • test:存放单元测试智能合约文件的目录
  • hardhat.config.ts:Hardhat 配置文件,查看详细配置信息请移步到此:hardhat.org/hardhat-run…
1
2
bash复制代码# 编译合约
npx hardhat compile

image.png

1
2
bash复制代码# 测试合约
npx hardhat test

image.png

1
2
bash复制代码# 部署合约
npx hardhat ignition deploy ./ignition/modules/Lock.ts

image.png

1
2
3
4
5
bash复制代码# 强制编译合约
npx hardhat compile --force

# 清除编译缓存
npx hardhat clean

将 Metamask 或 Dapp 连接到 Hardhat 网络

本地网络部署

1
2
bash复制代码# 启动本地节点
npx hardhat node

image.png

打开 Metamask 手动添加本地网络,如下图所示:

image.png

image.png

本地网络添加成功后,根据现有的20个测试账户,复制某个账户私钥,打开 Metamask 选择添加账户按钮,导入私钥添加新账户,如下图所示:

image.png

image.png

image.png

image.png

image.png

1
2
bash复制代码# 部署本地网络
npx hardhat ignition deploy ./ignition/modules/Lock.ts --network localhost

image.png

image.png

测试网部署(推荐)

下载安装 Ganache

image.png

如需详细了解Ganache工具的安装和使用,请移步到此:54web3.cc/blog/induct…

修改配置文件hardhat.config.ts

image.png

image.png

代码实现

编写合约

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
js复制代码// 创建合约文件:contract/TodoContract.sol

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.24;

// 合约名:TodoContract
contract TodoContract {
struct Todo {
uint256 id;
address author;
string message;
uint256 timestamp;
}

// 事件:NewTodo,用于记录新创建的待办事项
event NewTodo(
uint256 todoID,
address indexed from,
string message,
uint256 timestamp
);

// 事件:SendMoneyToContract,用于记录向合约发送以太币的事件
event SendMoneyToContract(
uint256 todoID,
address receiver,
string message,
uint256 timestamp
);

// 状态变量:todoID,用于记录待办事项的ID
// 状态变量:todoList,用于存储待办事项列表
// 状态变量:owner,用于记录合约的拥有者
uint256 public todoID;
Todo[] public todoList;
address payable public owner;

// 构造函数:初始化合约时设置合约的拥有者
constructor() payable {
owner = payable(msg.sender);
}

// 修饰符:onlyOwner,用于限制只有合约的拥有者才能调用该函数
modifier onlyOwner() {
require(
msg.sender == owner,
"\u5fc5\u987b\u662f\u5408\u7ea6\u6240\u6709\u8005"
);
_;
}

// 函数:withdraw,用于提取合约中的以太币
function withdraw() public payable onlyOwner {
uint256 balance = address(this).balance;
payable(msg.sender).transfer(balance);
}

// 函数:getBalance,用于查询合约中的以太币余额
function getBalance() public view returns (uint256) {
return address(this).balance;
}

// 函数:getTodoList,用于获取待办事项列表
function getTodoList() public view returns (Todo[] memory) {
return todoList;
}

// 函数:published,用于发布新的待办事项
function published(string memory _message) public payable {
todoID += 1;
Todo memory item = Todo(todoID, msg.sender, _message, block.timestamp);
todoList.push(item);
emit NewTodo(todoID, msg.sender, _message, block.timestamp);

uint256 payAmount = 0.1 ether;
require(msg.value >= payAmount, "\u4f59\u989d\u4e0d\u8db3");
(bool success, ) = payable(address(this)).call{value: payAmount}("");
require(success, "\u5411\u5408\u7ea6\u6c47\u6b3e\u5931\u8d25");
emit SendMoneyToContract(todoID, msg.sender, _message, block.timestamp);
}

// 函数:receive,用于接收以太币
receive() external payable {}
}

编译合约

1
bash复制代码npx hardhat compile

通过编译合约文件,我们会得到 Bytecode(字节码)和 ABI( Application Binary Interface)通过 ABI 可与已经部署的合约进行交互。

image.png

创建部署文件

1
2
3
4
5
6
7
8
9
10
11
js复制代码// ignition/modules/TodoContract.ts

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

const TodoContractModule = buildModule("TodoContractModule", (m) => {
const todoContract = m.contract("TodoContract");

return { todoContract };
});

export default TodoContractModule;

部署合约命令

1
bash复制代码npx hardhat ignition deploy ./ignition/modules/TodoContract.ts --network ganache

在线测试合约

在线打开Remix IDE 以太坊智能合约开发工具,创建合约文件TodoContract.sol,将写好的合约代码复制粘贴进来,保存后会自动编译。

image.png

image.png

image.png

前端开发

钱包登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
js复制代码// 如需获取完整代码,文末附 github 仓库下载地址
const connectWallet = async () => {
try {
if (!window.ethereum) {
ElNotification({
title: "提示",
message: "请先安装浏览器插件 Metamask",
type: "warning",
});
return
}

const provider = new ethers.BrowserProvider(window.ethereum) // 获取提供者
const signer = await provider.getSigner() // 获取钱包签名
account.value = await signer.getAddress() // 获取钱包地址
const network = await provider.getNetwork()
chainId.value = network.chainId.toString()
// 保存相关信息到本地
localStorage.setItem("chainId", chainId.value)
localStorage.setItem("account", account.value)
} catch (error) {
console.log('连接钱包失败:', error)
}
}
1
2
3
4
5
js复制代码// 监听钱包账户变化
window.ethereum.on("accountsChanged", function (accounts: string[]) {
account.value = accounts[0]
localStorage.setItem("account", account.value)
})

查询余额

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
js复制代码// 导入 ABI 接口文件
import contractABI from '../artifacts/contracts/TodoContract.sol/TodoContract.json'

// 获取合约账户余额
const getBalance = async () => {
if (!window.ethereum) {
ElNotification({
title: "提示",
message: "请先安装浏览器插件 Metamask",
type: "warning",
});
return
}

try {
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
account.value = await signer.getAddress()
const todoContract = new ethers.Contract(contractAddr, contractABI.abi, provider)
const count = await todoContract.getBalance()
balance.value = ethers.formatEther(count)
console.log('获取余额', balance.value)
} catch (error: any) {
console.error('获取余额失败:', error)
ElMessage.error(error.reason || error.data?.message || error.message)
}
}

我要提款

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
js复制代码const getWithdraw = async () => {
if (!window.ethereum) {
ElNotification({
title: "提示",
message: "请先安装浏览器插件 Metamask",
type: "warning",
});
return
}

try {
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const todoContract = new ethers.Contract(contractAddr, contractABI.abi, signer)
await todoContract.withdraw()
} catch (error: any) {
console.error('获取提款失败:', error)
}
}

发布消息

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
js复制代码const publishMsg = async () => {
if (!window.ethereum) {
ElNotification({
title: "提示",
message: "请先安装浏览器插件 Metamask",
type: "warning",
});
return
}

try {
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const todoContract = new ethers.Contract(contractAddr, contractABI.abi, signer)
todoContract.on("SendMoneyToContract", async (id, receiver, message, timestamp) => {
localStorage.setItem("todoCount", id)
console.log("%d %s", id, receiver)

const todoList = localStorage.getItem("todoList")
let list: Todo[] = []
if (todoList) {
list = JSON.parse(todoList)
}
const todo = new Todo(id, receiver, message, timestamp)
list.push(todo)
localStorage.setItem("todoList", JSON.stringify(list))
})

const params = { value: ethers.parseEther('0.1') }
const tx = await todoContract.published(msg.value, params)
await tx.wait()
ElMessage.success("发布消息成功")
setTimeout(() => {
location.reload()
}, 2000)
} catch (error: any) {
console.error('发布消息失败:', error)
}
}

消息列表

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
js复制代码// 获取消息列表
const getTodoList = async () => {
if (!window.ethereum) {
ElNotification({
title: "提示",
message: "请先安装浏览器插件 Metamask",
type: "warning",
});
return
}

try {
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const todoContract = new ethers.Contract(contractAddr, contractABI.abi, signer)
const list = await todoContract.getTodoList()
if (list.length > 0) {
list.map((item: Todo) => {
const id = item.id
const time = item.timestamp
const todo = new Todo(id, item.author, item.message, time)
todoList.value.push(todo)
})
}

const count = list.length
localStorage.setItem("todoCount", count)
todoCount.value = list.length
} catch (error) {
console.error('获取消息列表失败:', error)
}
}

结语

dApp 开发是一项综合性的工作,需要掌握多个领域的知识。通过学习不同平台和工具的文档、参与实际项目,以及不断改进和总结经验,你可以逐步成为一名优秀的 dApp 开发者。后续会输出一系列完整的 dApp 实战项目开发练习教程,涉及到技术栈有 React、Next.js、Wagmi、Vue3、Nuxt.js 等等。如果此文对看官们有一丢丢帮助,请点个赞👍或分享支持一下。

github 仓库:github.com/jackchen012…

参考资料

  • Metamask 入门教程
  • Vue3 官网
  • Web3 研习社
  • Vite 官网
  • Hardhat 官网
  • Ganache 入门教程
  • Solidity 中文文档
  • Ethers.js 英文文档
  • Remix IDE 入门教程

本文转载自: 掘金

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

【排序算法】希尔排序 基本思想 图解排序过程 代码实现 性能

发表于 2024-04-23

本篇文章来聊一聊希尔排序。

基本思想

上篇文章我们学习了折半插入排序,该排序算法的原理是在顺序插入查找插入过程中使用折半查找法从而提高插入效率,为此,我们可以思考一下是否还有办法能够使插入的效率更高呢?

基于此,希尔排序诞生了,希尔排序的基本思想为:

先将整个待排序序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录”基本有序”时,再对全体记录进行一次直接插入排序。

图解排序过程

现有如下的一个序列:

在这里插入图片描述

我们以5为间隔,将原序列分成若干个子序列:

在这里插入图片描述

此时81、35、41就组成了一个子序列,我们对该子序列进行插入排序,结果如下:

在这里插入图片描述

因为81数值比较大,若按顺序插入或者折半插入,需要经过多次的比较才能够到达指定的插入位置,而通过希尔排序,只进行了两次比较,就让35和81非常接近最终的插入位置,这样的效率无疑是很高的。

此时35、41、81已经有序,我们再以5为间隔,得到一个子序列:

在这里插入图片描述

此时得到94、17、75组成的子序列,对其进行插入排序,结果如下:

在这里插入图片描述

再以同样的方式得到一个子序列:

在这里插入图片描述

对11、95、15进行插入排序,结果如下:

在这里插入图片描述

同样的,再以5为间隔得到子序列:

在这里插入图片描述

此时子序列中只有两个元素96、28,这并不影响,继续进行插入排序即可:

在这里插入图片描述

最后一次排序,结果为:

在这里插入图片描述

经过了一系列的操作之后,序列中比较大的元素基本都往后靠了,比较小的元素基本都往前靠了,但是序列仍然是无序的,接下来我们以3为间隔,继续划分序列:

在这里插入图片描述

此时得到一个子序列:35、28、75、58、95,对该序列进行插入排序:

在这里插入图片描述

以同样的方式执行上述过程,最终结果为:

在这里插入图片描述

现在的序列虽然还是无序的,但已经非常接近有序的结果了,我们最后以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
c复制代码typedef struct {
int key;
}ElemType;

typedef struct{
ElemType *n;
int length;
}SSTable;

SSTable CreateTable(){
SSTable st;
st.n = (ElemType*) malloc(sizeof(ElemType) * 14);
st.length = 13;
st.n[1].key = 81;
st.n[2].key = 94;
st.n[3].key = 11;
st.n[4].key = 96;
st.n[5].key = 12;
st.n[6].key = 35;
st.n[7].key = 17;
st.n[8].key = 95;
st.n[9].key = 28;
st.n[10].key = 58;
st.n[11].key = 41;
st.n[12].key = 75;
st.n[13].key = 15;
return st;
}

希尔排序算法实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
c复制代码void ShellInsert(SSTable t,int dk){
int i,j;
for(i = dk + 1;i <= t.length;++i){
if(t.n[i].key < t.n[i - dk].key){
//将当前元素存入哨兵位置
t.n[0] = t.n[i];
for(j = i - dk;j > 0 && t.n[0].key < t.n[j].key;j = j - dk){
t.n[j + dk] = t.n[j];
}
t.n[j + dk] = t.n[0];
}
}
}

void ShellSort(SSTable t,int dlta[],int n){
int i;
//以增量序列为间隔,进行t趟排序
for(i = 0;i < n;++i){
ShellInsert(t,dlta[i]);
}
}

希尔排序算法其实和直接插入排序算法没啥区别,只是间隔变为了dk,其它的都一样,所以注释就不写了,如果你掌握了直接插入排序,并且搞懂了上面的图解,这个算法对你而言就没有难度。

测试代码:

1
2
3
4
5
6
7
8
9
c复制代码int main(int argc, char const *argv[]){
SSTable t = CreateTable();
//定义增量序列
int dlta[] = {5,3,1};
Traverse(t);
ShellSort(t,dlta,3);
Traverse(t);
return 0;
}

前面也说了,这个增量序列是随意的,但最后必须为1,且其它增量值无除1之外的公因子,所以,你也可以定义增量序列为:int dlta[] = {7,5,3,1};,或者定义为:int dlta[] = {4,3,2,1};,还可以定义为:int dlta[] = {10,7,4,1};,都是没有任何问题的。

性能分析

不难发现,希尔排序的效率是由增量序列决定的,其时间复杂度的计算过程难度较高,这里就直接给出了。在最坏情况下,希尔排序的时间复杂度为O(n2);最好情况下,其时间复杂度为O(n),所以,其平均时间复杂度约为O(n1.3)。

对于空间复杂度,因为其只使用了一个空的元素空间用于做哨兵位置,所以其空间复杂度为O(1)。

本文转载自: 掘金

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

Ansible工具安装和基本使用

发表于 2024-04-23

Ansible 是一款功能强大且易于使用的IT自动化工具,可用于配置管理、应用程序部署和云端管理。它使用无代理模式(agentless mode)来完成任务,这意味着您无需在目标主机上安装任何额外的软件。Ansible 通过 SSH 连接到目标主机并执行任务。

什么是无代理模式?

Ansible 的无代理模式是一种无需在目标主机上安装任何额外软件的管理方式。与传统的基于代理的配置管理工具不同,Ansible 通过 SSH 连接到目标主机并执行任务。

Ansible 的工作原理
可以概括为以下几个步骤:

  • 解析 Playbook:Ansible 首先会解析 Playbook 文件,其中定义了要执行的任务列表。Playbook 使用 YAML 编写,YAML 是一种易于阅读和编写的语言。
  • 建立连接:Ansible 会使用 SSH 连接到目标主机。连接信息在主机清单中定义,主机清单是一个 YAML 文件,其中包含了主机及其相关信息。
  • 执行任务:Ansible 会根据 Playbook 中的任务列表逐个执行任务。每个任务都使用 Ansible 模块来完成,Ansible 模块是用于执行特定任务的代码块。
  • 处理结果:Ansible 会处理任务的执行结果,并根据需要进行后续操作。

Ansible 的核心组件
Ansible 的核心组件包括:

  • Playbook:Playbook 是 Ansible 中用于定义任务列表的文件。Playbook 使用 YAML 编写。
  • 模块:模块是 Ansible 中用于执行特定任务的代码块。Ansible 提供了大量的内置模块,还可以创建自定义模块。
  • 主机清单:主机清单是 Ansible 中用于定义要管理的主机组的文件。主机清单是一个 YAML 文件,其中包含了主机及其相关信息。
  • 变量:变量用于存储和传递数据。Ansible 支持多种类型的变量,包括事实变量、inventory 变量、Playbook 变量和角色变量。
  • 模板:模板用于生成配置文件和其他文本文件。Ansible 使用 Jinja2 模板引擎。
  • 角色:角色是 Ansible 中用于组织任务和变量的集合。角色可以用于共享代码和提高代码的可重用性。

Ansible 的优势

  • Ansible 具有以下优势:
  • 易于使用:Ansible 使用 YAML 编写,YAML 是一种易于阅读和编写的语言。
  • 功能强大:Ansible 可用于执行各种任务,并且可以扩展以满足新的需求。
  • 无代理:Ansible 不需要在目标主机上安装任何额外的软件。
  • 开源:Ansible 是开源的,这意味着它是免费的并且可以自由修改和分发。

1.安装教程

1.首先第一步配置网络源(需要扩展包)

1
js复制代码[root@localhost ~]# yum install -y ansible

image.png

安装完成之后查看是否成功

1
2
3
4
5
js复制代码rpm -ql ansible	列出他的所有相关文件

rpm -qc ansible 列出配置文件

ansible-doc -l 查看它的所有模块

2实验

以下实验环境

3台Centos7

1台用于部署ansible服务器

2台用来被控制(其中一台免密登录,一台需用账户和密码登录)

主机名映射

1
js复制代码[root@ansible ~]# vim /etc/hosts

image.png

1.设置ssh-key

SSH密钥(Secure Shell key)是SSH(Secure Shell)协议中用于身份验证的凭证。与传统的基于密码的身份验证相比,它提供了一种更安全的方式连接到远程服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
js复制代码[root@localhost ~]# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
47:b6:e3:55:27:5c:8f:93:03:be:fc:87:28:a8:6d:cc root@localhost.localdomain
The key's randomart image is:
+--[ RSA 2048]----+
| . .|
| . o +.|
| o . O o|
| o o o = |
| S + + |
| + o o . |
| o. o . o .|
| oE . . |
| ... |
+-----------------+

image.png

查看是否成功

1
2
js复制代码[root@localhost ~]# ls .ssh/
id_rsa id_rsa.pub
1
2
3
4
5
6
7
8
9
10
11
12
js复制代码[root@localhost ~]# ssh-copy-id 192.168.93.112
The authenticity of host '192.168.93.112 (192.168.93.112)' can't be established.
ECDSA key fingerprint is e8:64:5f:06:f8:8c:63:6d:c9:eb:73:7d:78:d5:93:2b.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.93.112's password:

Number of key(s) added: 1

Now try logging into the machine, with: "ssh '192.168.93.112'"
and check to make sure that only the key(s) you wanted were added.
  • ssh-copy-id 192.168.93.112
  • 尝试在本地计算机和 IP 地址为 192.168.93.112 的服务器之间设置基于 SSH 密钥的身份验证。这样您就无需在每次使用 SSH 连接到服务器时输入密码。
2.定义主机清单

[root@localhost ~]# vim /etc/ansible/hosts

image.png

3.测试连通性
免密登录

ping的结果显示绿色就是成功的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码[root@localhost ~]# ansible localhost -m ping	对本机进行测试
localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}
[root@localhost ~]# ansible host1 -m ping 对host1进行测试

The authenticity of host 'host1 (192.168.93.112)' can't be established.
ECDSA key fingerprint is e8:64:5f:06:f8:8c:63:6d:c9:eb:73:7d:78:d5:93:2b.
Are you sure you want to continue connecting (yes/no)? yes
host1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"

image.png
简洁化显示
[root@localhost ~]# ansible host1 -m ping -o host1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"}

  • ansible :这是 Ansible 命令行工具。
  • host1 :指定要 ping 的目标计算机的主机名或 IP 地址。
  • -m ping :此选项告诉 Ansible 使用 ping 模块,该模块尝试 ping 目标主机。
  • -o选项简化输出

用户名密码登录

1
js复制代码[root@localhost ~]# ansible host2 -m ping -o -u root -k
  • host2 :这是 Ansible 将定位的主机或主机组的名称。在本例中,它是一个名为 host2 的主机。
  • -m ping :这指定您要使用 ping 模块。 ping 模块通常用作测试主机连接的简单方法。
  • -o :此标志告诉 Ansible 仅运行 playbook 一次。它对于 ping 模块不是必需的,但在其他上下文中可能很有用。
  • -u root :指定连接到目标主机时要使用的远程用户。在本例中,它是 root 。
  • -k :此标志告诉 Ansible 提示输入 SSH 密码。如果您尚未设置 SSH 密钥以进行无密码身份验证,这会非常有用。

出错的情况

image.png

1
2
3
js复制代码host2 | FAILED! => {
"msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host's fingerprint to your known_hosts file to manage this host."
}

解决办法

这个时候需要登录一次 再使用上边的命令才可以成功(因为你的密码自动输出给yes/no的选项中)

1
2
3
4
5
6
7
8
9
10
11
js复制代码[root@localhost ~]# ansible host2 -m ping 
The authenticity of host 'host2 (192.168.93.113)' can't be established.
ECDSA key fingerprint is e8:64:5f:06:f8:8c:63:6d:c9:eb:73:7d:78:d5:93:2b.
Are you sure you want to continue connecting (yes/no)? yes
host2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}

[root@localhost ~]# ansible all -m ping -o
all :这是 Ansible 中的特殊关键字,指您库存中的所有主机。

本文转载自: 掘金

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

1…262728…956

开发者博客

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