01、Flask框架服务器

render_template模板页面传参

@app.route('/index')
def index():user = {'username':'duke'}#将需要展示的数据传递给模板进行显示return render_template('index.html',title='我的',user=user)
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>{{ title }} - 博客</title>
</head>
<body><h1> Hello ,{{ user.username }} !</h1>
</body>
</html>

session机制的介绍:

RuntimeError: The session is unavailable because no secret key was set.  Set the secret_key on the application to something unique and secret. 运行错误:会话不可用,因为没有设置密钥。将应用程序上的secret_key设置为一些独特且秘密的内容。
from flask import Flask, request, sessionapp = Flask(__name__)
app.secret_key = "wristwaking"@app.route("/")
def index():session["login_user"] = "唤醒手腕"print(session.get("login_user"))return "index"

request的基本用途介绍

TypeError: The view function for 'index' did not return a valid response. The function either returned None or ended without a return statement.
错误类型:“索引”的视图函数没有返回有效的响应。函数返回None或没有rerun语句。

这是因为没有在URL参数中找到infor,所以request.args.get('infor')返回Python内置的None,而Flask不允许返回None。

解决方法很简单,我们先判断下它是不是None:

@app.route('/')
def hello_world():data = request.args.get('infor')if data==None:# do somethingreturn ''return data# 第二种方案,就是设置初始值# data = request.args.get("infor") # 设置默认值

还记得上面有一次请求是这样的吗? http://127.0.0.1:5000?infor=1&infor=2,仔细看下,infor有两个值。

@app.route('/')
def hello_world():data = request.args.getlist('infor')  # 返回一个listreturn str(data)

在request请求中,解析POST数据

# username = wristwaking  password = 5201314
@app.route("/form", methods=["POST"])
def form():print(request.form)# Immutable Multi Dict 不可改变的多个字典# ImmutableMultiDict([('username', 'wristwaking'), ('password', '5201314')])print(request.stream)# <werkzeug.wsgi.LimitedStream object at 0x000002A9AF2CE4F0>print(request.args)# ImmutableMultiDict([])return "success"

我们要想办法把我们要的username、password提取出来,怎么做呢?自己写?不用,Flask已经内置了解析器request.form

@app.route("/form", methods=["POST"])
def form():print(request.stream.read())# b'username=wristwaking&password=5201314'print(request.stream.read())# b'' 说明流只读取到一遍了return "success"

解释原因:request.stream.read()读取到的是字节流,我们进行解码,规则是utf-8,结果如下所示:

@app.route("/form", methods=["POST"])
def form():data = request.stream.read().decode(encoding="utf-8")print(data)# username=wristwaking&password=5201314return "success"

@app.route('/register', methods=['POST'])
def register():print(request.headers)# print(request.stream.read()) # 不要用,否则下面的form取不到数据print(request.form)print(request.form['name'])print(request.form.get('name'))print(request.form.getlist('name'))print(request.form.get('nickname', default='唤醒手腕'))return 'welcome'

响应JSON时,除了要把响应体改成JSON格式,响应头的Content-Type也要设置为 application/json

@app.route('/add', methods=['POST'])
def add():result = {'sum': request.json['a'] + request.json['b']}return Response(json.dumps(result),  mimetype='application/json')# 使用 jsonify 工具函数即可@app.route('/add', methods=['POST'])
def add():result = {'sum': request.json['a'] + request.json['b']}return jsonify(result)

Flask中appcurrent_app的理解

在Flask内部维护者两个线程隔离的栈,current_app指向了AppContext(应用上下文)中的栈顶。

线程有个叫做ThreadLocal的类,也就是通常实现线程隔离的类。而werkzeug自己实现了它的线程隔离类:werkzeug.local.Local,LocalStack就是用Local实现的。

LocalStack是flask定义的线程隔离的栈存储对象,分别用来保存应用和请求上下文。它是线程隔离的意思就是说,对于不同的线程,它们访问这两个对象看到的结果是不一样的、完全隔离的。这是根据pid的不同实现的,类似于门牌号。

而每个传给flask对象的请求,都是在不同的线程中处理,而且同一时刻每个线程只处理一个请求。所以对于每个请求来说,它们完全不用担心自己上下文中的数据被别的请求所修改。

request指向了RequestContext(请求上下文)栈顶,当请求进入的时候,Request对象被压入栈,从而request有了指向处理请求,接下来会判断AppContext栈顶是否为空,若为空则向栈中压入一个AppContext对象,即app

从而current_app就有了指向,所以我们在项目中使用是没有报错的,而我们上面的代码不是在请求中实现的,所以AppContext栈顶为空,current_app并没有指向一个AppContext对象。

02、Flask文件传输

from flask import Flask, requestfrom werkzeug.utils import secure_filename
import osapp = Flask(__name__)# 文件上传目录
app.config['UPLOAD_FOLDER'] = 'static/uploads/'
# 支持的文件格式
app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}  # 集合类型# 判断文件名是否是我们支持的格式
def allowed_file(filename):return '.' in filename and \filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']@app.route('/')
def hello_world():return 'hello world'@app.route('/upload', methods=['POST'])
def upload():upload_file = request.files['image']if upload_file and allowed_file(upload_file.filename):filename = secure_filename(upload_file.filename)# 将文件保存到 static/uploads 目录,文件名同上传时使用的文件名upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename))return 'info is '+request.form.get('info', '')+'. success'else:return 'failed'if __name__ == '__main__':app.run(port=5000, debug=True)

app.config中的config是字典的子类,可以用来设置自有的配置信息,也可以设置自己的配置信息。函数allowed_file(filename)用来判断filename是否有后缀以及后缀是否在app.config['ALLOWED_EXTENSIONS']中。

客户端上传的图片必须以image标识。upload_file是上传文件对应的对象。

  1. app.root_path获取server.py所在目录在文件系统中的绝对路径。

  2. upload_file.save(path)用来将upload_file保存在服务器的文件系统中,参数最好是绝对路径,否则会报错(网上很多代码都是使用相对路径,但是笔者在使用相对路径时总是报错,说找不到路径)

  3. os.path.join()用来将使用合适的路径分隔符将路径组合起来。

我们用python客户端测试下:

import requestsfile_data = {'image': open('Lenna.jpg', 'rb')}user_info = {'info': 'Lenna'}resp = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=file_data)print(resp.text)

要控制上产文件的大小,可以设置请求实体的大小,例如:

app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB

不过,在处理上传文件时候,需要使用try:... except:...

如果要获取上传文件的内容可以:

file_content = request.files['image'].stream.read()

03、Flask后端鉴权方案

Cookie是存储在客户端的记录访问者状态的数据。具体原理,请见 http://zh.wikipedia.org/wiki/Cookie ,常用的用于记录用户登录状态的session大多是基于cookie实现的。

@app.route('/add')
def login():resp = Response('add cookies')resp.set_cookie(key='name', value='唤醒手腕', expires=time.time()+6*60)return resp@app.route('/show')
def show():return request.cookies.__str__()@app.route('/delete')
def del_cookie():resp = Response('delete cookies')resp.set_cookie('name', '', expires=0)return resp

由上可以看到,可以使用Response.set_cookie添加和删除cookie,expires参数用来设置cookie有效时间,它的值可以是datetime对象或者unix时间戳,笔者使用的是unix时间戳。

res.set_cookie(key='name', value='letian', expires=time.time()+6*60)
# 上面的expire参数的值表示cookie在从现在开始的6分钟内都是有效的。

04、代理IP的原理

当我们对某些网站进行爬取的时候,我们经常会换IP来避免爬虫程序被封锁。这些代理IP地址是如何获取的呢?其实很简单,目前网络上有很多IP代理商,例如神龙,天启,芝麻等等,这些代理商一般都会提供透明代理,匿名代理,高匿代理。本文就讲讲各种代理 IP 背后的原理

代理IP的介绍

代理实际上指的就是代理服务器,英文叫作 proxy server,它的功能是代理网络用户去取得网络信息。

形象地说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给 Web 服务器,Web 服务器把响应传回给我们。

如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥,此时本机不是直接向 Web 服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后由代理服务器再发送给 Web 服务器,接着由代理服务器再把 Web 服务器返回的响应转发给本机。

这样我们同样可以正常访问网页,但这个过程中 Web 服务器识别出的真实 IP 就不再是我们本机的 IP 了,就成功实现了 IP 伪装,这就是代理的基本原理。

代理类型:高匿 > 混淆 > 匿名 > 透明

代理IP一共可以分成4种类型,除了前面提到过的透明代理IP,匿名代理IP,高匿名代理IP,还有一种就是混淆代理IP。从基础的安全程度来说呢,他们的排列顺序是高匿 > 混淆 > 匿名 > 透明。

代理原理

代理类型主要取决于代理服务器端的配置,不同配置会形成不同的代理类型。在配置中,这三个变量REMOTE_ADDR,HTTP_VIA,HTTP_X_FORWARDED_FOR 是决定性因素。

(一)REMOTE_ADDR

REMOTE_ADDR 表示客户端的 IP,但是它的值不是由客户端提供的,而是服务器根据客户端的 IP 指定的。

如果使用浏览器直接访问某个网站,那么网站的 web 服务器(Nginx、Apache等)就会把 REMOTE_ADDR 设为客户端的 IP 地址。

如果我们给浏览器设置代理,我们访问目标网站的请求会先经过代理服务器,然后由代理服务器将请求转化到目标网站。那么网站的 web 代理服务器就会把 REMOTE_ADDR 设为代理服务器的 IP。

(二)X-Forwarded-For(XFF)

X-Forwarded-For 是一个 HTTP 扩展头部,用来表示 HTTP 请求端真实 IP。当客户端使用了代理时,web 代理服务器就不知道客户端的真实 IP 地址。为了避免这个情况,代理服务器通常会增加一个 X-Forwarded-For 的头信息,把客户端的 IP 添加到头信息里面。

X-Forwarded-For 请求头格式如下:

X-Forwarded-For: client, proxy1, proxy2

client 表示客户端的 IP 地址;proxy1 是离服务端最远的设备 IP; proxy2 是次级代理设备的 IP;从格式中,可以看出从 client 到 server 是可以有多层代理的。

如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:

X-Forwarded-For: IP0, IP1, IP2

Proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 Proxy2 转发请求。列表中并没有 IP3,IP3 可以在服务端通过 Remote Address 字段获得。我们知道 HTTP 连接基于 TCP 连接,HTTP 协议中没有 IP 的概念,Remote Address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP,在这个例子里就是 IP3。

(三)HTTP_VIA

via 是 HTTP 协议里面的一个header,记录了一次 HTTP 请求所经过的代理和网关,经过1个代理服务器,就添加一个代理服务器的信息,经过2个就添加2个。

正向代理 和 反向代理: 原理介绍

反向代理

1、用户发送请求到服务器(访问的其实是反向代理服务器,但用户不知道)

2、反向代理服务器发送请求到真正的服务器

3、真正的服务器将数据返回给反向代理服务器

4、反向代理服务器再将数据返回给用户


反向代理有什么用为什么要这么做:

作用:用户请求过多,服务器会有一个处理的极限。所以使用反向代理服务器接受请求,再用均衡负载将请求分布给多个真实的服务器。既能提高效率还有一定的安全性。

用途:如果不采用代理,用户的IP、端口号直接暴露在Internet(尽管地址转换NAT),外部主机依然可以根据IP、端口号来开采主机安全漏洞,所以在企业网,一般都是采用代理服务器访问互联网。

正向代理与反向代理最简单的区别:

正向代理隐藏的是用户,反向代理隐藏的是服务器

05、正向代理服务器

vue proxy 原理介绍

浏览器会因为同源策略跨域,但服务端不禁止,npm run dev 本来就是运行了服务器,所有利用服务器发送请求即可(将所有请求转发到自己的node服务器然后发送请求,即 代理

使用Node开发http正向代理服务器:

var http = require("http");
var url = require("url");// 会建立一个http服务器,并监听 8080 端口:http.createServer(function(req,res){console.log("start request:",req.url);var option = url.parse(req.url);option.headers = req.headers;// 当接收到请求信息时,从请求头发获取信息并进行转发:var proxyRequest = http.request(options, function(proxyResponse){// 其余就是对信息输出,以方便我们了解到代理是否生效、代理内容如何等:proxyResponse.on("data", function(chunk){console.log("proxyResponse length",chunk.length);});proxyResponse.on("end", function(){console.log("proxyed request ended");res.end();})res.writeHead(proxyResponse.statusCode,proxyResponse.headers);});req.on("data",function(chunk){console.log("in request length:",chunk.length);proxyRequest.write(chunk,"binary");})req.on("end",function(){console.log("original request ended");proxyRequest.end();})}).listen(8080);

06、Flask.py底层

个人总结吧,以前学东西总是不喜欢看源码,主要是英语不行,后来才知道源码的重要性,学会看源码才能真正的去探究底层的实现,况且现在很多第三方库都把源码的注释写的很清楚。

Flask.py文件相关底层的介绍

源码中Flask类进行继承了Scaffold类(脚手架),关于Scaffold类的__init__展示:

def __init__(self,import_name: str, #: The name of the package or module that this object belongsstatic_folder: t.Optional[t.Union[str, os.PathLike]] = None,static_url_path: t.Optional[str] = None,template_folder: t.Optional[str] = None,#: The path to the templates folder, relative to ···root_path: t.Optional[str] = None,):

:attr:root_path, to add to the template loader.

Flask对象实现了一个WSGI应用程序,并作为中心对象。它被传递给应用程序的模块或软件包的名称。一旦创建了它,它将作为视图函数、URL规则、模板配置等的中心注册表。

装饰器@route,装饰视图函数

底层原理:@app.route("/") def index(): -> app.add_url_rule("/", view_func=index)

项目启动app.run(),源码介绍

 def run(self,host: t.Optional[str] = None,# host: the hostname to listen on. Set this to ``'0.0.0.0'`` to have the server available externally as well.# 要收听的主机名。将其设置为``‘0.0.0.0’``,以使服务器在外部也可用。port: t.Optional[int] = None,# the port of the webserver. Defaults to ``5000`` or the port defined in the ``SERVER_NAME`` config variable if present.# webserver的端口。默认为``5000``或在``SERVER_NAME``配置变量中定义的端口。debug: t.Optional[bool] = None,# ebug: if given, enable or disable debug mode. See :attr:`debug`.load_dotenv: bool = True,# Load the nearest :file:`.env` and :file:`.flaskenv` files to set environment variables. Will also change the working directory to the directory containing the first file found.# 加载最接近的:file:`.env`和:file:`.flaskenv`文件,以设置环境变量。还将将该工作目录更改为包含找到的第一个文件的目录。**options: t.Any,) -> None:

07、app.testing底层

Note that if you are testing for assertions or exceptions in your application code, you must set app.testing = True in order for the exceptions to propagate to the test client.

请注意,如果您正在测试应用程序代码中的断言或异常,则必须设置app.testing = True,以便将异常传播到测试客户端。

Otherwise, the exception will be handled by the application (not visible to the test client) and the only indication of an AssertionError or other exception will be a 500 status code response to the test client. See the :attr:testing attribute. For example::

app.testing = True
client = app.test_client()

The test client can be used in a with block to defer the closing down of the context until the end of the with block. This is useful if you want to access the context locals for testing::

测试客户端可以在带有with中使用,以将上下文的关闭推迟到带有with结束为止。如果您想访问上下文本地语言进行测试,这一点非常有用::

with app.test_client() as c:rv = c.get('/?vodka=42')assert request.args['vodka'] == '42'

Additionally, you may pass optional keyword arguments that will then be passed to the application’s :attr:test_client_class constructor. For example::

from flask.testing import FlaskClientclass CustomClient(FlaskClient):def __init__(self, *args, **kwargs):self._authentication = kwargs.pop("authentication")super(CustomClient,self).__init__( *args, **kwargs)app.test_client_class = CustomClient
client = app.test_client(authentication='Basic ....')

08、env环境变量

os.getenv(key, default=None) 函数介绍:

如果环境变量的字典集没有存在对应的KEY,会读取默认值:“唤醒手腕”

import os
print(os.getenv("CONF", default="唤醒手腕"))

09、Flask线程底层

Flask 默认是单进程,单线程阻塞的任务模式,在项目上线的时候可以通过nginx+gunicorn 的方式部署flask任务

但是在开发的过程中如果想通过延迟的方式测试高并发怎么实现呢,其实非常简单:

app.run()中可以接受两个参数,分别是threaded和processes,用于开启线程支持和进程支持。

Flask 单线程阻塞的任务模式 测试:同时打开三个浏览器访问

from datetime import datetimefrom flask import Flask@app.route("/index")
def index():print('start : ', datetime.now())time.sleep(10)  # 模拟阻塞"""Flask 默认是单进程,单线程阻塞的任务模式"""print('end: : ', datetime.now())return "hello world"if __name__ == "__main__":app.run(load_dotenv=True)

运行结果:总共需要的时间是30多秒,说明是单线程阻塞的模式

start :  2022-02-15 14:57:42.714942
end: :  2022-02-15 14:57:52.728294
start :  2022-02-15 14:57:52.730206
127.0.0.1 - - [15/Feb/2022 14:57:52] "GET /index HTTP/1.1" 200 -
end: :  2022-02-15 14:58:02.730972
start :  2022-02-15 14:58:02.733863
127.0.0.1 - - [15/Feb/2022 14:58:02] "GET /index HTTP/1.1" 200 -
end: :  2022-02-15 14:58:12.745196
127.0.0.1 - - [15/Feb/2022 14:58:12] "GET /index HTTP/1.1" 200 -

源码写的太明白了,原来是ThreadingMixIn的实例以多线程的方式去处理每一个请求,这样对开发者来说,只有在启动app时将threaded参数设定为True,flask才会真正以多线程的方式去处理每一个请求。

  1. threaded : 多线程支持,默认为False,即不开启多线程;
  2. processes:进程数量,默认为1.

开启方式:

if __name__ == "__main__":app.run(load_dotenv=True, threaded=True)

10、Flask数据库案例

这个案例是进行与数据库的连接,启动Flask服务器应用,构建视图函数映射到响应的地址目录,并且建立pymysql的连接对象,进行数据库的操作,这边就是添加单词的相应的操作。

from datetime import datetime
import threading
import timefrom flask import Flask, request, redirectapp = Flask(__name__)import pymysqlapp.config['mysql'] = pymysql.connect(host='localhost',  # 连接名称,默认127.0.0.1user='pythondb',  # 用户名passwd='root',  # 密码port=3306,  # 端口,默认为3306db='pythondb',  # 数据库名称charset='utf8',  # 字符编码
)@app.route('/')
def Login():with open('index.html', 'r', encoding='utf-8') as f:html = f.read()return html@app.route('/data')
def ShowData():english = request.args['english'].strip().replace("\n", "")chinese = request.args['chinese'].strip().replace("\n", "")sentence_eng = request.args['sentence_eng'].strip().replace("\n", "")sentence_chi = request.args['sentence_chi'].strip().replace("\n", "")cursor = app.config['mysql'].cursor()sql = "SELECT * FROM word ORDER BY id DESC limit 1"cursor.execute(sql)id = cursor.fetchone()[0] + 1sql = "insert into word values(%s,%s,%s,%s,%s)"print(cursor.execute(sql, (id, english, chinese, sentence_eng, sentence_chi)))app.config['mysql'].commit()return redirect("http://localhost:8080/")if __name__ == "__main__":app.run(host='localhost', port=8080, load_dotenv=True)

对应的前端页面代码展示,其实就是简单构造一个表单进行提交的操作:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><form action="http://localhost:8080/data"><p><input name="english" placeholder="Word:"></p><p><input name="chinese"  placeholder="Chinese:"></p><p><input name="sentence_eng"  placeholder="sentence_eng:"></p><p><input name="sentence_chi"  placeholder="sentence_chi:"></p><input type="submit" value="Submit"></form><style>input{ width: 300px; height: 40px }</style>
</body>
</html>

唤醒手腕Python全栈工程师学习笔记(框架应用篇)相关推荐

  1. 唤醒手腕Python全栈工程师学习笔记(网络爬虫篇)

    唤醒手腕Python爬虫学习笔记,喜欢的同学们可以收藏下,谢谢支持. 01.基础语法知识点 字符串的分割 webString = 'www.baidu.com' print(webString.spl ...

  2. 唤醒手腕Python全栈工程师学习笔记(持久存储篇)

    这个篇目是"持久存储篇",讲的就是Python操作数据库,这边介绍3种数据库,分别是MySQL.Redis.Mongodb 1. Python操作MySQL数据库 MySQL属于传 ...

  3. 唤醒手腕Python全栈工程师学习笔记(底层原理篇)

    01.内建名称空间 在Python中,有一个内建模块,该模块中有一些常用函数,变量和类. 而该内建模块在Python启动后.且没有执行程序员所写的任何代码前,Python首先会自动加载该内建模块到内存 ...

  4. 唤醒手腕Python全栈工程师学习笔记(拓展调用篇)

    01.利用python操作摄像头 首先介绍安装opencv-python第三方库 OpenCV 是一个开源的计算机视觉库,OpenCV 库用C语言和 C++ 语言编写,可以在 Windows.Linu ...

  5. 唤醒手腕Python全栈工程师学习笔记(并发编程篇)

    01.Process进程基本介绍 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器. 什么是进程? 进程 ...

  6. 唤醒手腕Python全栈工程师学习笔记(微机实验篇)

    01.树莓派安装操作系统 官网下载网址:https://www.raspberrypi.org/downloads/ 官网为不同的电脑操作系统提供了烧录软件,大家可以根据不同的操作系统下载对应软件.而 ...

  7. Python全栈工程师学习笔记 | Django的模型层

    Model模型 模型是你的数据的唯一的.权威的信息源.它包含你所储存数据的必要字段和行为. 通常,每个模型对应数据库中唯一的一张表. 每个模型都是django.db.models.Model的一个Py ...

  8. python datetime需要安装_Python全栈工程师学习笔记 | Django的模型层

    Model模型 模型是你的数据的唯一的.权威的信息源.它包含你所储存数据的必要字段和行为. 通常,每个模型对应数据库中唯一的一张表. 每个模型都是django.db.models.Model的一个Py ...

  9. 路飞学城出品python全栈工程师学习感受

    随便写点感受,作为记录. 前段时间公司有意做运维自动化的项目,整个团队中没有会python的人,于是运维有一个基础任务就是学习python,从<路飞学院21天python课程>中知道了Al ...

最新文章

  1. 网页3D效果库Three.js学习[二]-了解照相机
  2. operator new在C++中的各种写法
  3. STL源码剖析—stl_config
  4. [C#.NET 拾遗补漏]14:使用结构体实现共用体
  5. dotnet watch+vs code提升asp.net core开发效率
  6. 大于3小于4的整数bleem_比三大,比四小的整数是存在的吗?
  7. 洛谷 P3387 【模板】缩点
  8. 用云来实现主机效率的最大化 CSC这家公司是怎么做的?
  9. 知乎高赞回答!财务小白快速上手报表分析(内含公式+模板)
  10. MES系统之生产管理系统功能介绍(源码)
  11. jdbc连接timesten_JDBC远程连接TimesTen
  12. Mysql安装步骤:
  13. DVD影视光盘制作全程指导
  14. c++ 可变参数 log 打印函数实现
  15. Python学习-通过斗鱼api获取弹幕
  16. 论如何写一份好的前端面试简历
  17. 10个学习Java的网站,肯定有你不知道的哦!
  18. 前端怎么画三角形_用CSS画一个三角形
  19. 2020-10《信息资源管理 02378》真卷(独家文字版),圈定章节考点+统计真题分布
  20. 使用docker安装ubuntu镜像

热门文章

  1. 这是一个悲伤的程序员爱情故事 ? !
  2. 设计模式 | 组合模式及典型应用
  3. 2021最新HarmonyOS鸿蒙系统应用开发之基础入门教程到实战—持续更新(第三节:鸿蒙的技术特征)
  4. php 水平越权,水平越权与垂直越权
  5. java毕业设计项目介绍 基于SSM+Vue数字化家谱网站管理系统
  6. DETR代码学习(五)之匈牙利匹配
  7. tomcat热加载、热部署-源码解析
  8. node生成自定义命令(yargs/commander)
  9. 绘画系统(14):【类】QRegion[官翻]
  10. cmd命令卸载sql server_Ubuntu下部署SQL Server 2017(安装及使用方法,卸载方法)