当前位置: 首页 > 编程日记 > 正文

Swagger 生成 PHP restful API 接口文档

需求和背景

需求:

为客户端同事写接口文档的各位后端同学,已经在各种场合回忆了使用自动化文档工具前手写文档的血泪史.
我的故事却又不同,因为首先来说,我在公司是 Android 组负责人,属于上述血泪史中催死人不偿命的客户端阵营.
但血泪史却是相通的,没有自动化文档的日子,对接口就是开发流程中最低效的环节.
因此决定使用 swagger 搭建由php注释生成文档的流程.

背景:

我们的 restful api 项目采用 phalcon 框架,整体结构很简单,我们只需要用 swagger 扫描 controller 目录即可.
下简称我们的 php api 项目为 php_api_project.
服务器采用 nginx.

搭建

先说下最终的文档生成流程会是什么样子,以便先有个整体的认识:
搭建完成后, 整个流程, 从文档生成到前端展现, 大体如下:

  1. 在php文件中写 swagger 格式的 /* 注释 /
  2. 用 swagger-php 内的 bin/swagger.phar 命令扫描 php controller 所在目录, 生成 swagger.json 文件
  3. 将 swagger.json 文件拷贝到 swagger-ui 中 index.html 指定的目录中
  4. 打开 swagger-ui 所在的 url, 就可以看到文档了. 文档中的各个 api 可以在该网址上直接访问得到数据.

实现此需求只需要 swagger 的如下两个项目:
swagger-php: 扫描 php 注释的工具. 内含一个不错的例子.
swagger-ui: 用以将扫描工具生成的 swagger.json 文件内容展示在网页上.

首先将这两个项目下载到本地:

$ git clone https://github.com/swagger-api/swagger-ui.git
$ git clone https://github.com/zircote/swagger-php.git

文档生成工具部署

说是部署,主要就是产生 bin/swagger 这个用来生成 swagger.json 文件的命令.
主要工作,就是用 composer 解决下依赖就可以了.
因为国内直接用 composer 比较蛋疼,所以最好设置下国内的那个 composer 源.
这样的话, 整个 文档生成工具的部署 就是下面三行命令:

$ cd swagger-php
$ composer config repo.packagist composer https://packagist.phpcomposer.com
$ composer update

只要中间不报错,就算部署完成了. 完成后可以生成一份文档试一下.
swagger-php 项目下的 Examples 目录下有一个示例php工程,里面已经用 swagger 格式写了各种接口注释, 我们来尝试生成一份文档.
执行下面命令:

$ cd swagger-php
$ mkdir json_docs
$ php ./bin/swagger ./Examples -o json_docs/

上面命令会扫描 Examples 目录中的php文件注释, 然后在 json_docs 目录下生成 swagger.json 文件.
这个 swagger.json 文件就是前端 swagger-ui 用来展示的我们的api文档文件.

NOTE: swagger-php 只是个工具,放在哪里都可以.

前端 swagger-ui 部署:

部署方法很简单,就三步:

1. 将 swagger-ui 项目中的 dist 文件夹拷贝到 php_rest_api 根目录下.

NOTE1: 只需要拷贝dist这一个文件夹就可以了.最好重命名下,简单起见,这里不再重命名.
NOTE2: 我们的项目根目录和 nginx 配置的 root 是同一个目录.其实不用放跟目录,只要放到一个不用跨域就跨域访问的目录就可以了. 为啥有跨域问题? 后面会讲.

2. 修改 dist 文件夹下的 index.html 文件,指定 swagger.json 所在目录

只改一行就可以.
简单起见,这里直接将 swagger.json 目录指定在 dist 目录下即可. 我们这里屡一下预设条件:
假设 php_api_project 项目的 host 是 api.my_project.com;
假设 php_api_project 项目在 nginx 中指定的 root 即为其根目录;
假设 swagger-ui 里的 dist 文件夹放在上述根目录中;
假设 swagger.json 文件就打算放在上述 dist 目录下 (php_api_project/dist/swagger.json) ;
那么 index.html 中把下面的片段改成这样:

      var url = window.location.search.match(/url=([^&]+)/);if (url && url.length > 1) {url = decodeURIComponent(url[1]);} else {<!-- 就是这行,改成你生成的 swagger.json 可以被访问到的路径即可 -->url = "http://api.my_project.com/dist/swagger.json";}

3. 拷贝 swagger.json 到上述目录中.

# 把 swagger-php_dir 这个,换成你的 swagger-php 录即可
cp swagger-php_dir/json_docs/swagger.json php_api_project/dist/

上述步骤完成后, 访问 http://api.my_project.com/dis... 就可以看到 Examples 那个小项目的 api 文档了.

编写 PHP 注释

swagger-php 项目的 Example 中已经有了很多相关例子,照着复制粘贴就可以了.
更具体的相关注释规则的文档,看这里:
http://bfanger.nl/swagger-exp...

假设我的项目 controller 所在目录为 php_api_project/controller/, 那么我只需要扫描这个目录就可以了,不用扫描整个 php 工程.

为了在 swagger.json 中生成某些统一的配置, 建立 php_api_project/controller/swagger 目录. 目录存放一个没有代码的php文件,里面只写注释.

我给这个文件取名叫 Swagger.php, 大体内容如下:

<?php/*** @SWG\Swagger(*   schemes={"http"},*   host="api.my_project.com",*   consumes={"multipart/form-data"},*   produces={"application/json"},*   @SWG\Info(*     version="2.3",*     title="my project doc",*     description="my project 接口文档, V2-3.<br>
以后大家就在这里愉快的对接口把!<br>
以后大家就在这里愉快的对接口把!<br>
以后大家就在这里愉快的对接口把!<br>
"*   ),**   @SWG\Tag(*     name="User",*     description="用户操作",*   ),**   @SWG\Tag(*     name="MainPage",*     description="首页模块",*   ),**   @SWG\Tag(*     name="News",*     description="新闻资讯",*   ),**   @SWG\Tag(*     name="Misc",*     description="其他接口",*   ),* )*/

如上所示,我的这个php文件一行php代码也没有,就只有注释,为了定义一些全局的swagger设置:

schemes: 使用协议 (可以填多种协议)
host: 项目地址, 这个地址会作为每个接口的 url base ,拼接起来一期作为访问地址
consumes: 接口默认接收的MIME类型, 我的例子中的 formData 对应post表单类型. 注意这是项目默认值,在单个接口注释里可以复写这个值.
produces: 接口默认的回复MIME类型. api接口用的比较多的就是 application/jsonapplication/xml.
@SWG\Info: 这个里面填写的东西,会放在文档的最开头,用作文档说明.
@SWG\Tag: tag是用来给文档分类的,name字段必须唯一.某个接口可以指定多个tag,那它就会出现在多组分类中. tag也可以不用在这里预先定义就可以使用,但那样就没有描述了. 多说无益,稍微用用就啥都明白了.

然后就是给每个接口编写 swagger 格式的注释了.还是举个栗子吧:

    /*** @SWG\Post(path="/user/login", tags={"User"},*   summary="登录接口(用户名+密码)",*   description="用户登录接口,账号可为 用户名 或 手机号. 参考(这个会在页面产生一个可跳转的链接: [用户登录注意事项](http://blog.csdn.net/liuxu0703/)",*   @SWG\Parameter(name="userName", type="string", required=true, in="formData",*     description="登录用户名/手机号"*   ),*   @SWG\Parameter(name="password", type="string", required=true, in="formData",*     description="登录密码"*   ),*   @SWG\Parameter(name="image_list", type="string", required=true, in="formData",*     @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Image")),*     description="用户相册. 好吧,没人会在登录时要求填一堆图片信息.这里是为了示例 带结构的数据, @SWG\Schema ,这个结构需要另行定义,下面会讲."*   ),*   @SWG\Parameter(name="video", type="string", required=true, in="formData",*     @SWG\Schema(ref="#/definitions/Video"),*     description="用户 呃... 视频? 同上,为了示例 @SWG\Schema ."*   ),*   @SWG\Parameter(name="client_type", type="integer", required=false, in="formData",*     description="调用此接口的客户端类型: 1-Android, 2-IOS. 非必填,所以 required 写了 false"*   ),*   @SWG\Parameter(name="gender", type="integer", required=false, in="formData",*     default="1",*     description="性别: 1-男; 2-女. 注意这个参数的default上写的不是参数默认值,而是默认会被填写在swagger页面上的值,为的是方便用swagger就地访问该接口."*   ),* )*/public function loginAction() {// php code} /*** @SWG\Get(path="/User/myWebPage", tags={"User"},*   produces={"text/html"},*   summary="用户的个人网页",*   description="这不是个api接口,这个返回一个页面,所以 produces 写了 text/html",*   @SWG\Parameter(name="userId", type="integer", required=true, in="query"),*   @SWG\Parameter(name="userToken", type="string", required=true, in="query",*     description="用户令牌",*   ),* )*/public function myWebPageAction(){// php code}

规则简单明了,看着代码大家就都懂了.不懂的话,去看文档吧...

上面 login 接口中用到了两个有结构的数据, 一个是 image 类型的数组, 一个是 video 类型的结构.
(其实结构化的参数只能在 in="body" 时才可以用,但这并不妨碍我们为了简化问题,把结构化数据格式化为 json 当字符串传递. 我们只要将这种结构展现在文档里就可以了)

这种有结构的东西 swagger 也可以用 php 注释定义:

<?php/*** @SWG\Definition(type="object", @SWG\Xml(name="Image"))*/
class Image {/*** @SWG\Property()* @var string*/public $url;/*** @SWG\Property(format="int32")* @var int*/public $height;/*** @SWG\Property(format="int32")* @var int*/public $width;}<?php/*** @SWG\Definition(type="object", @SWG\Xml(name="Video"))*/
class Video {/*** @SWG\Property()* @var string*/public $url;/*** @SWG\Property()* @var string*/public $thumb_url;/*** @SWG\Property(format="int32")* @var int*/public $length;/*** @SWG\Property(format="int64")* @var int*/public $size;}

这样当这两个类也被 swagger-php/bin/swagger 扫描到后,其他地方就可以正确引用到 Image 和 Video 为名字的这两个结构体了.

这样做的好处是,在接口参数文档中,这个结构会被展示出来,这样客户端同学就知道该传什么结构了.

我的接口栗子里都没有写 response 规则,是因为我们用 json 作为返回载体,返回错误码也是包含在这个 json 结构体里. 并且多数接口返回的json格式都很复杂,用 swagger 的 response 规则基本没法描述.

swagger 的 response 编写规则是按照 http 的 response code 来的 (404, 401 等), 总之对我们的接口来说,这套描述规则不好用.

因此我就直接舍弃了 response 描述, 直接用 swagger 就地请求接口看看返回了什么就是. 再不行就把接口的返回信息在 description 里大体描述一下.

文档写完后,就可以调用 swagger-php/bin/swagger 命令生成 swagger.json, 再拷贝到 swagger-ui 中你指定的那个目录中,就可以访问文档了.

NOTE:

大家应该已经看出来了,其实接口的注释不一定要写在接口上,凭空写注释,一样能生成文档.所以不必纠结各个注释放在什么地方. 比如 swagger 整体定义, tag 定义等,写在任意可以被扫描到的 php 文件中就可以了.

常用字段简要说明

这里只是自己理解加翻译的简要说明,更详细的字段说明,还是要去看文档.再次贴出文档:
http://bfanger.nl/swagger-exp...

接口描述 (@SWGGet, @SWGPost 等) 常用字段:

summary - string
接口的简要介绍,会显示在接口标头上,不能超过120个字符description - string
接口的详细介绍externalDocs - string
外部文档链接operationId - string
全局唯一的接口标识consumes - [string]
接口接收的MIME类型produces - [string]
接口返回的MIME类型,如 application/jsonschemes -    [string]
接口所支持的协议,取值仅限: "http", "https", "ws", "wss"parameters -    [Parameter Object | Reference Object]
参数列表

参数描述 (@SWGParameter) 常用字段:

name - string
参数名. 通过路径传参(in 取值 "path")时有注意事项,没用到,懒得看了...in - string
参数从何处来. 必填. 取值仅限: "query", "header", "path", "formData", "body"description - string
参数描述. 最好别太长type - string
参数类型. 取值仅限: "string", "number", "integer", "boolean", "array", "file"required - boolean
参数是否必须. 通过路径传参(in 取值 "path")时必须为 true.default - *
默认值. 在你打算把参数通过 path 传递时规矩挺多,我没用到.用到的同学自己看文档吧.

自动生成文档

此段写于 2018-04-27. 本文其他段落成于 2017-01-12. 方法仅限 git 项目.
今天有朋友问 php swagger 的使用方法, 又审了一遍此文, 才发现没有写自动生成这部分.
每次更新项目还要手动生成文档的话, 就太麻烦了. 这种操作岂能不自动化. 方法如下:

前提: 项目为 git 项目. 项目部署以 git 为基础.
原理: 使用 git 的 hook 机制 在适当节点执行文档生成命令.
时机: 有两种: 如果使用 git 部署项目, 则可以在部署时自动生成; 如果不是, 也可以考虑在 git commit 时自动生成.
步骤:

首先找到需要被扫描 controller 所在目录, 假设其为 /project_path/app/controllers/.
然后找到 swagger 生成的 json 格式文档应该放置的地方, 假设其为 /project_path/swagger/docs/.
那么生成文档的命令如下:

php /path/to/swagger-php/bin/swagger /project_path/app/controllers/ -o /project_path/swagger/docs/

那么直接把上面这句命令放在对应的项目中的 .git/hooks/post-update 脚本中即可, 如没有则新建.

好吧, 上面那行命令是为了一眼可以看出这行命令是做什么的. 但它有个坏处, 就是要把项目的绝对路径写死在脚本里, 这样这个脚本就不具有通用性. 上面这行命令可以更新为下面的这行等价命令:

proj_root=$(readlink -f $(dirname $(git rev-parse --git-dir))) && \
php /path/to/swagger-php/swagger-php/bin/swagger $proj_root/app/controllers/ -o $proj_root/swagger/docs/

如此, 则每次该项目代码执行 git pull 命令时, swagger 文档将会自动更新.

遇到的问题

跨域问题:

swagger 牛X的地方就是它可以在文档上就地访问接口并展示输出,对于调试和对接口非常的方便.但如果不想将 swagger-ui 部署在接口项目下,那么在 swagger-ui 就地访问接口时,就会因跨域问题而请求不到结果. (Response Headers: no response from server; Response Body: no content).
这里不讲跨域问题怎么解决,只是给遇到上面的问题的各位一个思路,知道错误是由跨域产生的,就好解决了.

登录鉴权:

如果要将文档放在公网,直接暴露自己的接口可不太合适. 因此要给文档的访问地址做鉴权.
因为安全性要求不高,并且公司较小开发组人员不多,我就直接用了 nginx 提供的 http basic auth 做了登录鉴权.
老规矩,先贴官方文档:
https://www.nginx.com/resourc...
这样做鉴权很简单,分分钟搞定. 不详述, 就两步:

1. 首先用 htpasswd 命令,为需要访问文档的同学生成帐号名和密码:

$ htpasswd -cb your/path/to/api_project_accounts.db admin password_for_admin
$ htpasswd -b your/path/to/api_project_accounts.db liuxu 123456
$ htpasswd your/path/to/api_project_accounts.db xiaoming

-c 选项表示如果账号文件 ( api_project_accounts.db ) 不存在,则新建之. 因此创建第一个账号时一定要加 -c, 之后创建则一定不要再加 -c.
-b 参数表示明文指定密码,就是上面第一条和第二条命令中最后一个输入 (password_for_admin, 123456) . 因此如果不想明文指定,可以不加 -b, 像上面的第三条命令,就会和sudo命令一样,让你输入一段看不见的密码.
上面命令创建了三个账号, admin, liuxu, xiaoming, 并将账号密码保存在 api_project_accounts.db 文件中.

2. 然后在 nginx 的项目配置中给自己的这个访问地址开启 http basic auth 就可以了.

location /dist {auth_basic              "my api project login";auth_basic_user_file    your/path/to/api_project_accounts.db;
}

记得改完了重启 nginx:

nginx -s reload

这样一个简单的登录鉴权就建立起来了.

NOTE:

如果系统里没有 htpass 这个命令, 可以安装 apache 的 httpd 服务器软件, htpass 这个命令就包含在其中:

yum install httpd

参考:

[1] swagger 官网
[2] swagger 项目地址
[3] swagger ui 项目地址
[4] swagger php 项目地址
[5] swagger 文档参考地址

相关文章:

FPGA技术的未来发展:谁与AI平分秋色

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑作者 | 老石来源 | 老石谈芯&#xff08;公众号id&#xff1a;gh_5ce1d0cb1568&#xff09;责编 | Jane任何科学技术的发展和进步都离不开两个主要的推动力量&#xff0c;一个是相关领域各大公司的研发&#xff0c;另一个…

一体化设计让容灾变简单

容灾很难实现吗&#xff1f;容灾不仅包括技术方面的问题&#xff0c;而且涉及数据保护策略、投入产出比等方面的问题。从这个角度讲&#xff0c;对于大多数的中小型用户来说&#xff0c;容灾的实施确实比较困难。不过&#xff0c;爱数软件副总裁李基亮认为&#xff0c;容灾的实…

深度研究自然梯度优化,从入门到放弃 | Deep Reading

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑作者 | Cold Marie Wild译者 | 刘畅责编 | Jane出品 | AI科技大本营&#xff08;公众号id&#xff1a;rgznai100&#xff09;【导语】根据自然梯度的支持者提出一种建议&#xff1a;我们不应该根据参数空间中的距离来定义…

【Qt】QtCreator中关于Style Plugin Example没有效果的修改方法

1、问题描述 在QtCreator练习QStylePlugin的例子时,没有效果,原因是QPalette使用不当造成。 详见:https://blog.csdn.net/u010168781/article/details/88250451 2、解决方法 解决方法很简单,我们只是为了演示QStylePlugin的效果,然而QPushButton不能通过QPalette来改变…

最大公约数和最小公倍数的欧几里得算法

最大公约数的算法竟然如此简单&#xff0c;不说了&#xff0c;见代码 #include <stdio.h> int gcd(int a, int b) { if(b 0) return a; return gcd(b, a%b); } 简化后如下&#xff1a; int gcd(int a, int b) { return (b0 ? a: gcd(b, a%b)); } 而最小公倍数的也就为&a…

如何查看CISCO FWSM上ACL分区的空闲资源

在CISCO防火墙模块上有的时候在做策略NAT的时候会碰到如下的错误信息&#xff1a;输入:nat (inside) 1 access-list XYZ错误提示:ERROR: Unable to add Policy Rulesaccess-list XYZ 可以在配置的ACL中显示尤其在添加一些基于策略的NAT的时候&#xff0c;因为其可能会产生大量的…

强烈推荐一款Python可视化神器!

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑翻译 | Lemon来源 | Plotly出品 | Python数据之道 &#xff08;ID&#xff1a;PyDataRoad&#xff09;Plotly Express 入门之路Plotly Express 是一个新的高级 Python 可视化库&#xff1a;它是 Plotly.py 的高级封装&am…

【Qt】QIcon::fromTheme:从系统主题中获取图标

1、简介 函数原型 QIcon QIcon::fromTheme(const QString &name) QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)上述两个函数可以从系统主题中获取图标&#xff0c;后者可以在主题中找不到图标时&#xff0c;再使用自己定义的图标&#x…

检验EIGRP

路由器必须与其邻居建立邻接关系&#xff0c;EIGRP 才能发送或接收更新。EIGRP 路由器通过与相邻路由器交换 EIGRP Hello 数据包来建立邻接关系。 使用 show ip eigrp neighbors 命令来查看邻居表并检验 EIGRP 是否已与其邻居建立邻接关系。对于每台路由器&#xff0c;您应该能…

【Qt】通过QtCreator源码学习Qt(十):多国语言支持

1、获取系统支持的语言 QStringList uiLanguages;uiLanguages = QLocale::system().uiLanguages();2、从设置中获取想要显示的语言,尝试覆盖默认的 QString overrideLanguage = settings->value(QLatin1String("General/OverrideLanguage")).toString();if (!ove…

吴恩达最新斯坦福课程《深度学习》全部视频已送达,请签收!

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑责编 | Jane出品 | AI科技大本营&#xff08;公众号id&#xff1a;rgznai100&#xff09;【导语】2018 年秋季&#xff0c;吴恩达教授在斯坦福新开了一门 CS230《深度学习》课程&#xff0c;近期&#xff0c;该课程的视频…

mysql 5.7 修改root 密码

前言 MySQL5.7为了加强安全性&#xff0c;yum 安装后为root用户随机生成了一个密码&#xff0c;同时修改root密码上&#xff0c;也增加了一些校验&#xff0c;会报 Your password does not satisfy the current policy requirements 错误。 安装 rpm -ivh http://repo.mysql.co…

2-7-PatchesAdministration

/cdrom/sol_10_305_x86/s2/Solaris_10/Product 下是包含系统自带的补丁包 该目录下的包需要复制到/var/spool/pkg目录下再使用pkgadd才可以被安装cp -r SUNWi15cs /var/spool/pkgpkgadd SUNWi15cs2-7系统补丁管理补丁类型&#xff1a;standard patchesrecommended patchesfirmw…

【Qt】通过QtCreator源码学习Qt(十一):Utils::Icon,根据不同主题、不同状态变换图标

1、简介 在QtCreator中Utils::Icon封装的图标可以根据主题变换,还可以设置不同状态下的图标的颜色。不同状态下的颜色变换,由QIcon::addPixmap函数实现: void QIcon::addPixmap(const QPixmap &pixmap, Mode mode = Normal, State state = Off)2、源码分析 src/libs/…

Python_赋值和深浅copy

Python中赋值和深浅copy存储变化     在刚学python的过程中&#xff0c;新手会对python中赋值、深浅copy的结果感到丈二和尚。经过多次在网上查找这几个过程的解释说明以及实现操作观察&#xff0c;初步对这几个过程有了浅显的认识。以下内容仅是我在学习过程中遇到的问题&…

萌新养成 | AI科技大本营实习生招募计划

金三银四跳槽季这个时候需要做好准备的可不仅仅只有在职或者离职的萝卜尚未毕业的萌新也需要提早做准备了毕竟把自己修炼成一个优秀的萝卜也不是一件容易的事所以&#xff0c;放下你找对象、刷副本、世界游的想法加入我们为你量身定制的人才培养计划加入营长的团队&#xff0c;…

踏上云旅程 存储准备好了吗

在云计算的概念刚出现时&#xff0c;人们习惯将原始数据存在本地&#xff0c;而将备份数据放到云中。随着云计算技术和应用逐渐走向成熟&#xff0c;人们可能会把原始数据存在云中&#xff0c;而把备份数据放在本地。全球存储网络工业协会&#xff08;SNIA&#xff09;主席Wayn…

UCloud与NTT达成合作,提供可靠跨地域混合云服务

近日&#xff0c;UCloud宣布与NTT Communications东亚地区总部NTT Com Asia (以下简称“NTT Com Asia”) 达成合作伙伴协议。双方将发挥各自优势&#xff0c;通过UCloud云服务与NTT Communications企业级 Enterprise Cloud 服务互联互通&#xff0c;为国内企业以及进入中国的跨…

ICCV 2019论文投稿数破纪录,中科院、清华名列前茅,苹果垫底

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑整理 | 琥珀出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;昨日&#xff0c;作为计算机视觉领域里顶级会议这一的 International Conference on Computer Vision &#xff08;ICCV&#xff09;公布了 2019 年…

【linux】在CentOS7上更改端口号时报错:Job for sshd.service failed because the control process exited with error

1、问题描述 在在CentOS7上更改端口号时报错&#xff1a; Job for sshd.service failed because the control process exited with error code.See “systemctl status sshd.service” and “journalctl -xe” for details. 2、修改ssh端口号的方法 修改&#xff1a;$ sudo …

硅谷风投押注计算机网络安全市场

今天&#xff0c;新浪转载了一篇外媒报道&#xff0c;称随着一系列安全事件的集中爆发&#xff0c;风险投资家们业从计算机安全领域看到了机会&#xff0c;希望加大对这一市场的投资。 文章最后指未来几年可能上市的公司包括&#xff1a;梭子鱼、Proofpoint、Palo Alto Network…

Tensorflow框架是如何支持分布式训练的?

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑作者 | 杨旭东转载自知乎《算法工程师的自我修养》专栏Methods that scale with computation are the future of AI. —Rich Sutton, 强化学习之父大数据时代的互联网应用产生了大量的数据&#xff0c;这些数据就好比是石…

【linux】SELinux工具:semanage的安装和使用

1、安装 在ubuntu14.04上安装 sudo apt-get install policycoreutils在CentOS7上安装 sudo yum -y install policycoreutils-python2、semanage命令行参数 $ semanage --help semanage用于配置SELinux策略的某些元素&#xff0c;而不需要对策略源进行修改或重新编译。 位置…

在阿里云Kubernetes容器服务上打造TensorFlow实验室

简介 Jupyter notebook是强大的数据分析工具&#xff0c;它能够帮助快速开发并且实现机器学习代码的共享&#xff0c;是数据科学团队用来做数据实验和组内合作的利器&#xff0c;也是机器学习初学者入门这一个领域的好起点。 而TensorFLow是深度学习和机器学习最流行的开源框架…

PagedGeometry 笔记03

1. 创建草 PagedGeometry *grass new PagedGeometry(mCamera,50); grass->addDetailLevel<GrassPage>(100); // 在100单位内绘草。 GrassLoader *grassLoader new GrassLoader(grass); grass->setPageLoader(grassLoader); grassLoader->setH…

【Qt中文手册】QSortFilterProxyModel

1、说明 QSortFilterProxyModel类继承自QAbstractProxyModel是一个代理类,存在于另一模型Model和视图View之间,将另一个模型排序或者过滤后在视图上显示。 2、简单示例 没有使用代理的项视图模型代码如下 QTreeView *treeView = new QTreeView; MyItemModel *model = new…

吴恩达的Landing.ai又迎来一位AI大牛

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑整理 | 琥珀出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;近日&#xff0c;吴恩达在Twitter发消息称&#xff0c;欢迎王冬岩加入Landing.ai&#xff0c;负责公司的客户对接及战略伙伴合作&#x…

synchronize

1。synchronize方法的产生与vcl的局限性有关,因为vcl控件在同一时刻只能被单线程访问&#xff0c;如果多个线程同时访问vcl,vcl会出现问题。所以问了安全地访问vcl,Tthread类提供了一个方法叫synchronize&#xff0c;他可以让线程中的方法在主线程中执行&#xff0c;所以我们可…

Google BBR拥塞控制算法背后的数学解释 | 深度

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑作者 | 赵亚转载自CSDN网站杭州待了一段时间&#xff0c;回到深圳过国庆假期&#xff0c;无奈温州皮鞋?厂老板过节要回温州和上海&#xff0c;不在深圳&#xff0c;也就没有见着&#xff0c;非常遗憾&#xff01;国庆节…

【Linux】linux使用mplayer播放摄像头

1、安装mplayer 1.1 在ubuntu上安装mplayer sudo apt-get install mplayer1.2 在Centos7上安装mplayer 安装软件包&#xff1a; sudo yum localinstall http://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm安装mplayer&#xff1a; sudo yum ins…