1、什么是用户登录

票据有两层意思:

  • 如果用户持有票据 ,我们就认为用户的身份是合法的
  • 我们可以根据票据知道当前用户到底是谁

和身份证的意义是一样的,有身份证才说明你是合法公民,我们也就能知道你到底是谁。

身份证都有一个有效期,那么这个票据也需要有一个有效期

2、服务器用什么样的机制把票据返回到客户端上去,并存储在客户端里的呢?

对于我们的网站而言,服务器是将我们的票据信息写入到cookie中,并且把cookie返回到浏览器中存储起来。

3、什么是cookie

有时候,我们服务器会生成多个key-value就像我们的字典一样,这些key-value会随着我们的响应(也就是response)返回到我们的浏览器里面去,然后浏览器会把这些key-value存储起来,那么这些被浏览器存储起来的键值对呢,就叫做cookie,当然cookie并不是简单的一组key-value键值对,他还有他的相关特性。

重要的特性:

  • cookie失效:我们可以在服务器给cookie设置一个有效期,一旦超过了这个有效期,浏览器就不会在保存这样的cookie了。
  • 如果说一个浏览器,它存储由某一个网站写入到浏览器中的cookie的话,那么下一次只要我们再次通过浏览器发送这个请求访问网站,那么这次请求的信息中呢,将再次携带这个cookie,但前提条件是呢,cookie是没有失效的。

4、在flask中向浏览器写入cookie

make_response产生一个响应对象,然后调用response的set_cookie方法把键name值MR.7写入到cookie中并附加一个有效期,单位是秒,这里是100秒。也就是说这个cookie在浏览器中只会存储100秒,超过100秒这个cookie就失效了。最后我们把response对象返回到我们的客户端上去。

注意这里的过期时间是比我们的北京时间少8个小时的。在加上8个小时才是我们的北京时间。

5、cookie在什么情况下会失效

在两种情况下:

  • 设置了有效期,那么超过了这个有效期之后cookie就没有了
  • 如果我们不设置这个有效期的话,那么这个cookie是一次性的(一般浏览器关闭了之后这个cookie就消失了)

6、cookie的应用

  • 利用cookie来做用户登录之后票据的保存。
  • 现在用的最多的还是用来做广告的精准投放

简单说一下广告的精准投放

为什么推荐的是家电呢?是因为之前在京东买了电饭煲,京东就会通过cookie来记录我的偏好行为。

因为我们说过cookie机制,是保存在浏览器当中的,那么下一次只要再次通过这个浏览器去访问网站的话,那么cookie是会发向我们的服务器去的。所以记录了我喜欢电饭煲或者是家电的这样的cookie呢就发向了腾讯视频。那么腾讯视频得到了这个信息之后就会像我展现1号店的家电广告。

这里你们会发现一个很有意思的事,我是通过jd来买的电饭煲,那么这个cookie的写入是由jd写入的,那么为什么我在看腾讯视频的时候,腾讯视频知道我喜欢电饭煲呢?那么cookie本来的机制是哪个网站写入的cookie哪个网站才能获取这个cookie,那么腾讯是如果获取到京东的cookie的呢?如果我们自己做了一个网站,我们是拿不到jd的cookie的。更有意思的是这里投放的是1号店的,相当于1号店使用了jd的数据。现在的1号店和腾讯还有jd可以算是一家了。腾讯自己的电商做的不好,所以入股了jd,而jd收购了1号店,既然是一家,那么就好办了。有很多技术可以实现跨站cookie共享。下面可以看到我们有jd的cookie。


7、flask-login的使用

当用户登录成功之后我们要产生一个票据,并且把这个票据写入cookie中,我们不仅负责写入票据还要负责读取票据,并且要管理这个票据,整个的登陆机制是非常繁琐的,所以我们自己去用cookie实现这一整套的管理机制是非常不明智的,很幸运的是flask给我们提供了插件flask-login,可以完全用这个插件来管理登陆信息。

首先安装插件

导入插件

7.1、实例化LoginManager并初始化LoginManager

就如同我们之前的sqlalchemy的db的创建实例及初始化一样。写如下图红框所示的代码。

如果用户的身份验证通过,我们如何来保存用户的票据呢?

保存用户的票据信息需要我们从flask_login中导入另外的一个函数就是login_user

7.2、login_user模块

然后我们就需要把我们从数据库中查询到的user模型当做参数传入login_user中

下面我们需要了解一下login_user的一些机制

用户登录的实质是要向cookie中写入这样的票据。

我们在这里并不会直接的操作cookie,而是通过login_user间接的把用户票据写入到cookie中

login_user是一个高度封装的函数,但是它的实质依然是把用户的票据写入到cookie中。

这里我们需要考虑一下票据到底是什么东西,我们往cookie中写入的又是什么东西。

看上面代码我们知道我们往login_user中传入了一个我们自己定义的用户模型,那么是不是说login_user把我们用户模型中的所有数据全部都写入到了cookie中了呢?这个并不现实,因为user模型是我们自己定义的,可能数据非常的多,其中一些信息是根本不需要写入到cookie中的,那么最关键最应该写入到cookie中的是什么呢?是我们用户的id号,因为id号才能代表用户的身份。

那么问题来了,login_user这个函数他如何知道我们自己定义的user模型下面的这么多的数据哪一个是代表用户身份信息的id号呢?

7.3、login_user之get_id方法

所以flask login_user这个插件他要求我们在我们的用户模型内部定义一个函数,这个函数的名字是固定的叫get_id,在这个函数内部我们需要返回可以表示我们用户身份这样的一个字段。这里我们用的是下面这样的来表示用户的身份

再次说明:这里的函数名get_id是固定的,不是随便定义的。login_user除了要求你定义这个函数,还要求你定义一些其他的属性或者是函数。我们看login_user的源码可知道还需要定义一些别的属性或者是函数。下面是login_user的源码。

由上图可知,我们要在我们自己的用户模型类里面是按照login_user的要求把它的函数或者是属性全都定义一遍是非常的麻烦的,可以看到login_user已经帮我们定义好了的,他是定义在flask login_user内部的UserMixin这个类里面的。那么我们是不是可以让我们的用户模型继承UserMixin,那么我们的user模型就自动拥有了他定义好的数据或者是内置函数呢?就不需要我们在一个个定义了。

7.4、继承自UserMixin

看上图可知,继承完UserMixin之后呢,我们就不在需要def get_id(self)了

这里有一点要注意:如果我们不是用下图的id这样的字段名,来表示用户的身份的话,还是要重写def get_id(self)的。

因为UserMixin提供的def get_id(self)默认是用id号来表示用户的身份的,可以看之前的login_user的源码的图。

下面我们来测试一下是否成功!

进入登录页面,F12可以看到现在是没有任何值的。点击登录之后,可以在下图看到有cookie了。

点击登录完成后,这里就出现login_user他所写入的票据也就是我们的cookie。他的名字叫做session,他的value可以看到是明显加密过的。关于通过login_user写入的cookie他有一个特点就是这个cookie依然是一个一次性的cookie,当我们把整个的浏览器都关闭后(不是当前打开的页面关闭,而是整个浏览器关闭),他是会消失的。

如果我们想让这个cookie成为持续形cookie,就需要把remember=True

即使我们关闭了浏览器,这个cookie在一定时间内也是不会消失的。默认的情况是365天。

如果我们想更改这个时间,需要在我们的flask配置项中增加一个配置项就是REMEMBER_COOKIE_DURATION

7.5、视图函数权限控制(@login_required、@login_manager.user_loader)

下面我们要对某些视图函数做权限控制。

如果客户端没有存储cookie是不能访问下面的这个视图函数的。

实现思路:

  • 我们最容易想到的是,在my_gifts()视图函数的前面呢。首先去读取用户的票据信息,如果我们能通过读取cookie拿到用户的id号呢,我们就认为用户是已经登录过的,但是我们拿不到用户的id号就拒绝执行后续的相关代码。但是这样实现起来非常的麻烦,而且要在每一个需要用户登录的视图函数中都要去写读取cookie和判断cookie的代码。
  • 使用装饰器,from flask_login import login_required

只要我们在上面打上@login_required那么这个视图函数就变成了必须要用户登录才能访问的视图函数

要使用@login_required还需要为我们的插件编写一个函数,

上图是从app.py这个文件中导入之前我们注册过的login_manager

上图的意义就是接受用户的id号,我们要通过get_user这个函数呢,来返回用户的用户模型。那么这个用户模型可以通过传入进来的id号,去数据库里面查询来获取到用户模型。这里用get而不用filter_by是因为uid是用户的主键,如果是查询主键是没有必要用filter_by的。

我们这里只定义一个单独的函数是不行的,因为这个函数是需要被我们的flask_login所调用的,如果我们不做任何标识的话flask_login是不知道调用这个get_user方法的,所以我们要在这里打上@login_manager.user_loader

我们之前是定义过的login_manager所以要导入一下。

下面我们来测试一下 :

先注释装饰器@login_required

我们加上之后发现访问不了了

如果我们要实现不同用户的权限分级,要改写login_required的相关机制来实现,这里不多说。

我们直接给用户看这样的页面是不好的,我们应该引导用户,页面直接跳向登陆页面,然后登陆完成之后还要自动返回到我们的my_gifts页面上来。不管大网站还是小网站都遵循这样的引导再返回的原则。

7.6、login_manager.login_view设置登录页面

那么我们要告诉flask_login这个插件哪个视图函数才是我们的登陆页面。

我们只需要想上图一样,把我们登陆页面的地址复值给login_manager.login_view,这样flask_login插件就知道哪个视图函数才是我们的登陆函数。加入上面代码后的效果如下。自动跳转到下面的登陆页面

7.7、login_manager.login_message

出现上面的的一段英文提示,出现的原因是,当我们用未登录的状态尝试去访问要求登陆的视图函数的时候,flask_login会自动闪现一条消息,这个消息就是我们看到的这段文本。默认情况这段文本是英文的,我们也可以定义成中文的,

只需要加入上图红框login_manager.login_message='请先登录或注册' 代码即可,

7.8、登录成功后的next

当我们登陆完成之后发现用户停留的页面还是在登陆页面,但是我们用户最初访问的并不是登陆页面而是my_gifts页面

那么怎么跳转回去呢?我们观察一下现在的URL

除了正常的登陆页面之外呢,flask_login插件在?号后面附加了一个next查询参数,这个next的参数记录的就是我们要跳转回去的地址就是我们的my_gifts,那么既然有了这个next的参数,我们就可以

使用request.args来获取要跳转页面的url地址,可以获取?号后面的相关查询参数。request.form是获取POST提交过来的表单数据。

这一这里要return,如果不return的话,是不会结束这样的视图函数的执行的。

但是上图红线处的代码有问题,如果已经登录的用户直接访问http://localhost:81/login地址,我们就取不到查询next,那么redirect(next)就会报错。所以需要加上判断。

代码写完了,功能上是没问题了。但是存在安全隐患

7.9、重定向攻击

如果用户人为的直接在浏览器中输入了网址,人为的指定了next的参数,指向了我们的腾讯网

然后会进行登录,登陆成功之后会自动重定向到我们的腾讯网。那么这就是我们说的重定向攻击

如何防止这种非法的重定向呢?我们可以在后面加上一个判断next.startwith('/')来判断next是否是以/开头的,如果不是我们就重定向指向到我们的首页。任意一个条件不满足我们都应该把它重定向到我们的首页中去。

7.10、current_user

如何在视图函数中拿到用户的uid号?如果用户登录了,那么uid号是存储在我们的cookie中的。那么在这里我们同样不需要操作cookie来获取uid号。而是直接通过flask_login给我们设计好的一个变量直接获取用户的相关信息。

current_user这个变量的命名就可以知道,代表了当前访问我们网站的用户

这里我们来分析一下current_user,其实就是我们实例化后的user模型

但是明明我们在cookie中存储的只是用户的uid,那么为什么current_user是一个user模型呢?

关键的地方就在于我们之前定义的get_user方法。我们确实在cookie中只存储了uid,但是我们定义了这样的函数,在这个函数的内部的实现把我们的id号,转换成了我们的对象模型。所以我们的current_user最后就变成了我们的用户模型。

我们查看一下current_user的源码:

发现跟我们之前的current_app和request是一样的。

7.11、SQLAlchemy的回滚

下面我们继续编写鱼豆有关的代码:下图是业务逻辑

下面代码不要写死是0.5,避免硬编码就要使用配置项来定义鱼豆0.5

上面的代码可以看出我们是没有进行事务的操作的,但是SQLAlchemy天然的是支持事务的,所以我们在commit之前的代码都不会被真正的执行添加到数据库中的。但是上面代码还是有问题的,我们是没有进行数据的回滚(rollback)

为什么一定要执行db.session.rollback(),是因为如果我们的程序执行到db.session.commit()这里出现了错误,又没有执行rollback的话,那么不仅仅是这次的插入失败了,后续的其他操作SQLAlchemy也是会失败的。所以建议大家一定要回滚起来!

所以以后只要使用db.session.commit()就一定要用try except给包裹起来并在except里面执行db.session.rollback()

7.12、上下文管理器 之使用contextmanager装饰器

请跳转我的从这里拆分出来的博客文章

python使用@contextmanager来定义上下文管理器(一篇文章,彻底明白!码文并茂,简单明了)

所以最后我们上面复杂的try except并执行db.session.rollback()就被我们采用上下文管理器@contextmanager的方式给简化了。

7.13、flask_login之logout_user

使用logout_user实现用户登出

其实logout_user()没有多神秘,就是把浏览器内部的cookie给清空了。

7.14、忘记密码

我们之前讲过,如果要确定用户的身份,是要在cookie中写入一个票据,但是用户通过重置URL的方式进入我们的网站是没有这个cookie的。所以我们要想办法在URL上做一些文章,标记出来到底是哪一个用户需要重置他的密码,这里我整理出来了三个方案。

  • 第一个方案就是在URL中的?号后面携带一个参数,这个参数是用户的一个id号或者就是他的账号,这样的话当用户点击重置URL的网址进入到我们的网站之后,我们就可以通过id号获取账号,知道到底哪个用户要重置密码,这个方案容易最容易操作,但是存在缺点,这个ID号或者账号很容易被串改,还有就是暴露了用户的id。
  • 第二个方案同样是要在URL中附加用户的id号,但是我们可以把ID给加密。这个加密后的id号只有我们的网站可以给他反译出来,最终拿到用户的id,其他人是无法把它破解的。
  • 第三个方案我们可以在服务器缓存或者在mysql写入一个个的键值对,key是一条随机字符串,value为用户id,我们可以把key这个随机字符串附加到url中发向用户,当用户点击这个url之后,我们是可以接收到这样的随机字符串的,根据key-value原则,有key就可以拿到value,这样我们就可以通过随机字符串间接的拿到用户的id号,从而确定用户的身份。

那么第二种和第三种方案有什么不同么?最大的不同就是:在第二种方案中我们把用户的真正信息写入到了邮件中并且发送到了客户端,而第三种并没有把用户的信息发送过去,我们只是用了一个随机的字符串,真正的信息还是存储在服务器中的。这就是他们两个最大的不同。

第一种是不推荐的,第二种和第三种是差不多的,这个世界没有绝对安全的东西,但是这两种要设置过期时间,超过过期时间这个URL是无效的。缩短有效期时间可以一定程度上加强安全性。

7.15、first_or_404()

我们现在查询用户输入的邮箱是不是存在的时候,是使用下面的代码.first()实现的,然后要在判断是不是相等之类的。

我们可以使用下图所示,由flask提供的.first_or_404()方法来触发。

下面我们输入数据库中不存在的邮箱,可以看到如下图所示并不会报错,他会给我们跳到Not Found这样的页面中来。

假如说我们使用的是first(),这次查询没有找到任何的结果,那么这个user将会被赋一个空值,后面的流程会继续往下执行,只不过user是没有任何值的,但是我们要是使用first_or_404()的话,这里要注意,一般这个查询没有找到任何结果的话,后续的代码是不会再执行了。因为在first_or_404()这个方法的内部会抛出一个异常来,python代码在中间的某个部分抛出了异常之后,这个抛出异常代码后面的代码是不会执行的。可以看到下图,如果我们写first()的话,user找不到的时候我们还需要手动抛出一个异常来,而first_or_404()就非常的智能,可以避免我们自己去处理user为空的情况,他直接帮我们抛出了这个异常。

下面我们深入的研究一下first_or_404()具体做了什么事,flask又做了什么事

first_or_404()内部实现了first()的封装,他还做了一个很简单的判断,如果取到的值是空的话,他会抛出一个异常来,抛出异常是通过Abort对象来实现的,(一会我们说一下如何通过对象来实现方法的调用的),在Abort的内部是会抛出一个异常的,这个异常不是我们之前上上图红框写的Exception()类,是Exception()的子类,也是一个特定的类,叫HttpException

下面我们看一下源代码,

我们在flask_sqlalchemy的__init__.py文件中找到BaseQuery这个类,在BaseQuery下面有一个first_or_404()方法,这个是flask给我们封装的一个快捷的方法

可以看到在这个方法的内部,就是对first()方法的一个封装,如果查询结果为None的话,会abort(404),我们继续看一下abort的源码,

在abort这个函数内部,他在内部调用了_aborter这个方法,在这里我们以为是方法或者是函数的调用,结果下面的代码告诉我们_aborter是一个对象,那么问题就来了,如果说_aborter是一个函数或者说是一个方法,return后面那么写我们都很容易理解,但是问题是_aborter是一个对象。对象为什么也可以像函数或者是方法这样来调用呢?如果你想把一个对象当做一个函数一样来调用,那么这个对象内部必须实现一个特殊的方法就是__call__()方法,我们来看Aborter()的源码,

一旦我们在一个类的内部实现了这个特殊的__call__()方法之后,我们就可以把这个对象当做函数一样来调用。

python callable可调用对象(实现__call__)

点击上面的连接可以查看详细的知识点讲解

我们来解释一下抛出的self.mapping的异常是HTTPException的子类。

我们来看一下mapping是如何实现的,这个mapping的定义是在我们当前类的下面,我们就要关注这个类的构造函数,也就是上面的__init__方法,可以看到mapping=default_exceptions,往上去寻找发现下图所示,可以看到定义为了一个空的字典

但是这个空的字典是没有意义的,它肯定在某个地方装载了这样的字典,也就是它下面的方法。

_find_exceptions()方法回去扫描当前模块下的所有的类,如果说有类是继承自HTTPException的那么他就会把它存入我们的default_exceptions中,从而完成我们的default_exceptions的初始化工作,可以看到上面的issubclass(obj,HTTPException)就是在判断某一个类是不是属于HTTPException类型的。

这样的话default_exceptions中将装载所有我们定义的各种各样的异常,只要这个异常是属于HTTPException的,所以我们将mapping=default_exceptions,这样mapping就拥有了所有的异常对象,最后通过[code]也就是http状态码找到对应的HTTP异常,并且把它给抛出来。

Python issubclass() 函数

下面我们继续剖析下图所示的异常与现实界面的关系

我们first_or_404()抛出的不是HTTPException基类而是继承自HTTPException的一个子类,我们看到它传入进来的code的值就是404

我们发现NotFound这个类的状态码是404

我们可以看到description就是现实的文本,那么他是怎么样显示在页面中的呢?

我们之前说过页面输出什么完全取决于一个非常重要的对象,就是response响应对象。所以我们推测最后构建的响应对象一定是读取了NotFound下面的description,我们继续看咱们构建的视图函数,下面红框内抛出异常后,后面的代码将不会执行

那么这个响应对象不会从我们的视图函数return来构建,那么异常的response是在哪里构建的呢?答案就藏在HTTPException这个基类的里面,我们来F12看一下,在这个类里面有一个get_response方法,异常的response就是在get_response中构建的。

上图就是读取了我们的description的文本。


7.16、@app.app_errorhandler(404)装饰器

比如我们想在调用first_or_404()之后,不要显示flask默认的404页面,而是显示我们自己定义的页面。

一种方式是try except然后return模板自己写的404的html

但是这样写的问题就是:如果每次都在first_or_404()的地方都要try except然后再render_template就非常的麻烦

另一种方式是:

在_init__.py中写一个函数,我们希望无论在我们代码编写的任何一个地方,只要抛出了一个状态码为404的HTTPException的话,那么我们的flask框架呢就可以自动的帮我们执行not_found()函数,从而实现我们自己定义的返回404.html页面。

我们只需要在上面打上@app.app_errorhandler(404)装饰器,就可以实现监控所有状态码为404的http异常,从而实现我们自定义的404页面。如果我们抛出的是500这样的异常的话,not_found()函数是不会执行的。需要注意的是我们可以在函数内实现任意代码需求的并不是一定要实现render_template的

这里使用的思想就是AOP思想,面向切片编程,这个思想其实就是我们不要在零散的在每一个会出现404的地方去try except,而是把所有的处理代码集中到一个地方,这里我们就是集中到了函数中,这样的实现我们是借助了flask给我们写好的app_errorhandler装饰器,

cookie在flask中的应用、flask-login模块的使用(login_user、@login_required、@login_manager.user_loader)current_user相关推荐

  1. Flask 中的session 和 cookie

    文章目录 前言 什么是Cookie? Cookie 和 Session 什么是Seesion? 什么是token? Cookie和session的区别 token和session的区别 Flask中使 ...

  2. flask中的session伪造问题

    前言 这段时间刷题遇见过几次在flask框架中伪造session的,也经常和其他flask框架的两大漏洞SSTI和py反序列化结合来考,今天就写这篇文章学习一下在ctf题目里flask中的sessio ...

  3. flask中的信号机制

    2019独角兽企业重金招聘Python工程师标准>>> flask中信号机制 Flask信号允许特定的发送端通知订阅者发生了什么.既然知道发生了什么,那我们可以知道接下来该做什么了. ...

  4. Flask框架(flask中对cookie的处理(设置cookie、获取cookie、删除cookie))

    在Flask中对cookie的处理 1. 设置cookie: 设置cookie,默认有效期是临时cookie,浏览器关闭就失效 可以通过 max_age 设置有效期, 单位是秒 resp = make ...

  5. Flask 中内置的 Session

    Flask中的Session非常的奇怪,他会将你的SessionID存放在客户端的Cookie中,使用起来也非常的奇怪 1. Flask 中 session 是需要 secret_key 的 from ...

  6. flask中的request

    1.request是什么? 简单来说,它就是flask的封装的一个对象,这个对象包含着前端请求所带的所有信息.既然说它是一个对象,那么它肯定是有一些熟悉,和方法的,下面就来介绍下request里的熟悉 ...

  7. flask中的CBV , flask-session在redis中存储session , WTForms数据验证 , 偏函数 , 对象里的一些小知识...

    flask中的CBV , flask-session在redis中存储session , WTForms数据验证 , 偏函数 , 对象里的一些小知识 flask中的CBV写法 后端代码 # 导入vie ...

  8. flask中的CBV和FBV

    flask中CBV使用 from flask import Flask, viewsapp = Flask(__name__)class Login(views.MethodView):methods ...

  9. Flask 【第七篇】Flask中的wtforms使用

    一.简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 安装: pip3 install wtforms 二.简单使用wtfo ...

最新文章

  1. TensorRT Samples: GoogleNet
  2. jQuery调用WCF服务传递JSON对象
  3. Java ServletContextListener用法
  4. Spring Data JPA 实例查询
  5. Matlab仿真炮弹飞行轨迹——探究射弹参数对飞行轨迹的影响
  6. python新特性_Python3.6正式版新特性预览
  7. 因漏洞Dropbox用户邮件地址被泄露给垃圾邮件发送者
  8. Sonar问题及解决方案汇总
  9. linux-网络数据包抓取-tcpdump
  10. html5 h264 websocket,2.5 在WebSocket中使用HTML5媒体
  11. 易飞erp postgre mysql_pgadmin 执行sql
  12. 金山办公推出协同办公全家桶 WPS升级为超级工作入口
  13. 【无标题】123321
  14. 如何查看你的浏览器的Flash版本
  15. 程序猿怎样变身IT讲师
  16. mars2d解决底图上下拖动超出边界出现灰色底
  17. 【沃顿商学院学习笔记】领导力——Business Impact:03商业如何驱动影响力的案例Some Cases
  18. Axure 设计App界面
  19. PowerBi - TopN+帕累托
  20. mysql实践周心得_实践周心得体会4篇

热门文章

  1. MATLAB旋转和平移时,以白色填充多出来的区域
  2. (转载)总结一下SQL语句中引号(')、quotedstr()、('')、format()在SQL语句中的用法
  3. 乐学成语——android(一)
  4. 青海湖进入冰封季 宛如“冰河时代”
  5. 浅谈文字编码和Unicode(下)[转]
  6. 【汇正财经顾晨浩】2023 年风电交付大年,聚焦大型化及深远海
  7. Sublime Text3常用快捷键整理(快速编程及调试)
  8. python串口编程整理
  9. PCL 惯性矩和偏心率
  10. 五分钟让C盘多出三十个G