RESTful API入门学习笔记
文章目录
- 参考资料
- 1. 什么是RESTful风格?
- 1.1 REST全称
- 1.1 实例说明
- 2. RESTful的概念
- 2.1 资源与URI
- 2.2 统一资源接口
- 2.2.1 GET
- 2.2.2 POST
- 2.2.3 PUT
- 2.2.4 DELETE
- 2.3 资源的表述
- 2.3.1 json格式
- 2.3.2 XML格式
- 2.3.3 常用的设计
- 2.3.3.1 在URI里边带上版本号
- 2.3.3.2 使用URI后缀来区分表述格式
- 2.3.3.3 如何处理不支持的表述格式
- 2.4 资源的链接
- 2.5 状态的转移
- 2.5.1 应用状态与资源状态
- 2.5.2 应用状态的转移
- 3. RESTful API 设计指南
- 3.1 协议
- 3.2 域名 (Domain name)
- 3.3 版本(Versioning)
- 3.4 路径(Endpoint)
- 3.5 HTTP动词
- 3.6 过滤信息(Filtering)
- 3.7 状态码(Status Codes)
- 3.8 错误处理(Error handling)
- 3.9 返回结果
- 3.10 Hypermedia API
- 3.11 其他
参考资料
RESTful API 设计指南 作者: 阮一峰
菜鸟教程 RESTful 架构详解
--------------------------------------正文分割线-----------------------------------------------------
1. 什么是RESTful风格?
1.1 REST全称
REST是REpresentational State Transfer的缩写(一般中文翻译为表述性状态转移),REST 是一种体系结构,而 HTTP 是一种包含了 REST 架构属性的协议,为了便于理解,我们把它的首字母拆分成不同的几个部分:
- 表述性(REpresentational): REST 资源实际上可以用各种形式来进行表述,包括 XML、JSON 甚至 HTML——最适合资源使用者的任意形式;
- 状态(State): 当使用 REST 的时候,我们更关注资源的状态而不是对资源采取的行为;
- 转义(Transfer): REST 涉及到转移资源数据,它以某种表述性形式从一个应用转移到另一个应用。
简单地说,REST 就是将资源的状态以适合客户端或服务端的形式从服务端转移到客户端(或者反过来)。在 REST 中,资源通过 URL 进行识别和定位,然后通过**行为(即 HTTP 方法)**来定义 REST 来完成怎样的功能。
1.1 实例说明
在平时的 Web 开发中,method 常用的值是 GET 和 POST,但是实际上,HTTP 方法还有 PATCH、DELETE、PUT 等其他值,这些方法又通常会匹配为如下的 CRUD 动作:
CRUD 动作 | HTTP 方法 |
---|---|
Create | POST |
Read | GET |
Update | PUT 或 PATCH |
Delete | DELETE |
尽管通常来讲,HTTP 方法会映射为 CRUD 动作,但这并不是严格的限制,有时候 PUT 也可以用来创建新的资源,POST 也可以用来更新资源。实际上,POST 请求非幂等的特性(即同一个 URL 可以得到不同的结果)使其成一个非常灵活地方法,对于无法适应其他 HTTP 方法语义的操作,它都能够胜任。
在使用 RESTful 风格之前,我们如果想要增加一条商品数据通常是这样的:
/addCategory?name=xxx
但是使用了 RESTful 风格之后就会变成:
/category
这就变成了使用同一个 URL ,通过约定不同的 HTTP 方法来实施不同的业务,这就是 RESTful 风格所做的事情了,为了有一个更加直观的理解,引用一下来自how2j.cn的图:
2. RESTful的概念
要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。
下面结合REST原则,围绕资源展开讨论,从资源的定义、获取、表述、关联、状态变迁等角度,列举一些关键概念并加以解释。
- 资源与URI
- 统一资源接口
- 资源的表述
- 资源的链接
- 状态的转移
2.1 资源与URI
REST的名称"表现层状态转化"中,省略了主语。“表现层"其实指的是"资源”(Resources)的"表现层"。
**所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。**它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
任何事物,只要有被引用到的必要,它就是一个资源。资源可以是实体(例如手机号码),也可以只是一个抽象概念(例如价值) 。
下面是一些资源的例子:
- 某用户的手机号码
- 某用户的个人信息
- 最多用户订购的GPRS套餐
- 两个产品之间的依赖关系
- 某用户可以办理的优惠套餐
- 某手机号码的潜在价值
要让一个资源可以被识别,需要有个唯一标识,在Web中这个唯一标识就是URI(Uniform Resource Identifier)。
URI既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用URI来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。URI的设计应该遵循可寻址性原则,具有自描述性,需要在形式上给人以直觉上的关联。这里以github网站为例,给出一些还算不错的URI:
- https://github.com/git
- https://github.com/git/git
- https://github.com/git/git/blob/master/block-sha1/sha1.h
- https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08
- https://github.com/git/git/pulls
- https://github.com/git/git/pulls?state=closed
2.2 统一资源接口
RESTful架构应该遵循统一接口原则,统一接口包含了一组受限的预定义的操作,不论什么样的资源,都是通过使用相同的接口进行资源的访问。接口应该使用标准的HTTP方法如GET,PUT和POST,并遵循这些方法的语义。
如果按照HTTP方法的语义来暴露资源,那么接口将会拥有安全性和幂等性的特性,例如GET和HEAD请求都是安全的, 无论请求多少次,都不会改变服务器状态。而GET、HEAD、PUT和DELETE请求都是幂等的,无论对资源操作多少次, 结果总是一样的,后面的请求并不会产生比第一次更多的影响。
下面列出了GET,DELETE,PUT和POST的典型用法:
2.2.1 GET
安全且幂等
获取表示
变更时获取表示(缓存)
200(OK) - 表示已在响应中发出
204(无内容) - 资源有空表示
301(Moved Permanently) - 资源的URI已被更新
303(See Other) - 其他(如,负载均衡)
304(not modified)- 资源未更改(缓存)
400 (bad request)- 指代坏请求(如,参数错误)
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
2.2.2 POST
不安全且不幂等
使用服务端管理的(自动产生)的实例号创建资源
创建子资源
部分更新资源
如果没有被修改,则不过更新资源(乐观锁)
200(OK)- 如果现有资源已被更改
201(created)- 如果新资源被创建
202(accepted)- 已接受处理请求但尚未完成(异步处理)
301(Moved Permanently)- 资源的URI被更新
303(See Other)- 其他(如,负载均衡)
400(bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
2.2.3 PUT
不安全但幂等
用客户端管理的实例号创建一个资源
通过替换的方式更新资源
如果未被修改,则更新资源(乐观锁)
200 (OK)- 如果已存在资源被更改
201 (created)- 如果新资源被创建
301(Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他(如,负载均衡)
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
2.2.4 DELETE
不安全但幂等
删除资源
200 (OK)- 资源已被删除
301 (Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他,如负载均衡
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
409 (conflict)- 通用冲突
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
2.3 资源的表述
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
资源的表述包括数据和描述数据的元数据,例如,HTTP头"Content-Type" 就是这样一个元数据属性。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
2.3.1 json格式
以github为例,请求某组织资源的json格式的表述形式:
response的body如下所示,为json格式
{"login": "github","id": 9919,"node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=","url": "https://api.github.com/orgs/github","repos_url": "https://api.github.com/orgs/github/repos","events_url": "https://api.github.com/orgs/github/events","hooks_url": "https://api.github.com/orgs/github/hooks","issues_url": "https://api.github.com/orgs/github/issues","members_url": "https://api.github.com/orgs/github/members{/member}","public_members_url": "https://api.github.com/orgs/github/public_members{/member}","avatar_url": "https://avatars1.githubusercontent.com/u/9919?v=4","description": "How people build software.","name": "GitHub","company": null,"blog": "https://github.com/about","location": "San Francisco, CA","email": "support@github.com","is_verified": true,"has_organization_projects": true,"has_repository_projects": true,"public_repos": 341,"public_gists": 0,"followers": 0,"following": 0,"html_url": "https://github.com/github","created_at": "2008-05-11T04:37:31Z","updated_at": "2020-02-07T13:08:07Z","type": "Organization"
}
2.3.2 XML格式
假如github也能够支持xml格式的表述格式,那么结果就是这样的:
2.3.3 常用的设计
2.3.3.1 在URI里边带上版本号
有些API在URI里边带上版本号,例如:
- http://api.example.com/1.0/foo
- http://api.example.com/1.2/foo
- http://api.example.com/2.0/foo
如果我们把版本号理解成资源的不同表述形式的话,就应该只是用一个URL,并通过Accept头部来区分。
以github为例,它的Accept的完整格式是:application/vnd.github[.version].param[+json]
对于v3版本的话,就是Accept: application/vnd.github.v3。对于上面的例子,同理可以使用使用下面的头部:
- Accept: vnd.example-com.foo+json; version=1.0
- Accept: vnd.example-com.foo+json; version=1.2
- Accept: vnd.example-com.foo+json; version=2.0
2.3.3.2 使用URI后缀来区分表述格式
像rails框架,就支持使用/users.xml或/users.json来区分不同的格式。 这样的方式对于客户端来说,无疑是更为直观,但混淆了资源的名称和资源的表述形式。
而django的DRF框架则如下,利用format这一param来确定返回数据的格式,如图所示:
format=api时,
format=json时,
2.3.3.3 如何处理不支持的表述格式
当服务器不支持所请求的表述格式,那么应该怎么办?若服务器不支持,它应该返回一个HTTP 406响应,表示拒绝处理该请求。
以github为例,展示了一个请求XML表述资源的结果:
2.4 资源的链接
我们知道REST是使用标准的HTTP方法来操作资源的,但仅仅因此就理解成带CURD的Web数据库架构就太过于简单了。
这种认知模式忽略了一个核心概念:“超媒体即应用状态引擎(hypermedia as the engine of application state)”。即忽视了超链接这一关键要素。
当浏览Web网页时,从一个连接跳到一个页面,再从另一个连接跳到另外一个页面,就是利用了超媒体的概念:把一个个把资源链接起来。要达到这个目的,就要求在表述格式里边加入链接来引导客户端。在《RESTful Web Services》一书中,作者把这种具有链接的特性成为连通性。
例如,在一个超市的项目中,/goods/的接口不仅要把商品的信息给出来,还要把商品类别,下一页的api也一同给出来,以此提高api的连通性。
2.5 状态的转移
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。通常情况下,它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
2.5.1 应用状态与资源状态
状态应该区分
应用状态
和资源状态
,客户端负责维护应用状态,而服务端维护资源状态。客户端与服务端的交互必须是无状态的,并在每一次请求中包含处理该请求所需的一切信息。
服务端不需要在请求间保留应用状态,只有在接受到实际请求的时候,服务端才会关注应用状态。这种无状态通信原则,使得服务端和中介能够理解独立的请求和响应。
通过这种设计,在多次请求中,同一客户端也不再需要依赖于同一服务器,方便实现高可扩展和高可用性的服务端。例如,服务器集群时,客户端就不必担心该向集群中的哪一台服务器请求数据。
但有时候也会做出违反无状态通信原则的设计,例如利用Cookie跟踪某个服务端会话状态,常见的是利用SESSIONID(会话id)来进行跟踪。这意味着,浏览器随各次请求发出去的Cookie是被用于构建会话状态的。
当然,如果Cookie保存的是一些服务器不依赖于会话状态即可验证的信息(比如认证令牌token),这样的Cookie也是符合REST原则的。
2.5.2 应用状态的转移
状态转移到这里已经很好理解了, "会话"状态不是作为资源状态保存在服务端的,而是被客户端作为应用状态进行跟踪的。客户端应用状态在服务端提供的超媒体的指引下发生变迁。服务端通过超媒体告诉客户端当前状态有哪些后续状态可以进入。
这些类似"下一页"之类的链接起的就是这种推进状态的作用——指引你如何从当前状态进入下一个可能的状态。
例如,第一页接口中,提供了第二页的接口指向,即进入下一个状态的入口
3. RESTful API 设计指南
3.1 协议
API与用户的通信协议,通常基于HTTPs协议(生产环境中)。
3.2 域名 (Domain name)
应该尽量将API部署在专用域名之下。
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/
3.3 版本(Versioning)
可以将API的版本号放入URL。
https://api.example.com/v1/
另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。
具体用哪种,视情况而定就行了。
3.4 路径(Endpoint)
路径又称"终点"(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
例如,有一个API提供电影(movie)的信息,还包括各种演员和导演的信息,则它的路径应该设计成下面这样。
- https://api.example.com/v1/movies
- https://api.example.com/v1/actors
- https://api.example.com/v1/directors
3.5 HTTP动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子,以movies数据集为例。
- GET /movies:列出所有电影
- POST /movies:新建一个电影
- GET /movies/ID:获取某个指定电影的信息
- PUT /movies/ID:更新某个指定电影的信息(提供该电影的全部信息)
- PATCH /movies/ID:更新某个指定电影的信息(提供该电影的部分信息)
- DELETE /movies/ID:删除某个电影
- GET movies/ID/actors:列出某个指定电影的所有演员
- DELETE /movies/ID/actors/ID:删除某个指定电影的指定演员
3.6 过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。
- ?limit=10:指定返回记录的数量
- ?offset=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- ?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。
比如,GET /movies/ID/actors 与 GET /actors?movie_id=ID 的含义是相同的,都是找出指定id的电影下的所有演员。
3.7 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
状态码的完全列表参见这里。
3.8 错误处理(Error handling)
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{error: "Invalid API key" }
3.9 返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
3.10 Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
{"link": {"rel": "collection https://www.example.com/movies","href": "https://api.example.com/movies","title": "List movies","type": "application/vnd.yourformat+json" }}
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
{"current_user_url": "https://api.github.com/user","authorizations_url": "https://api.github.com/authorizations",// ... }
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
{"message": "Requires authentication","documentation_url": "https://developer.github.com/v3" }
Django的DRF中,api Root也是基于该理念而设计的,如图所示
3.11 其他
(1)API的身份认证应该使用OAuth框架。
(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
------------------------------------------------------正文结束分割线--------------------------------------
RESTful API入门学习笔记相关推荐
- 中移物联网onenet入门学习笔记2:中移物联的通信格式
中移物联网onenet入门学习笔记2:中移物联的通信格式 中移物联网硬件接入协议:LWM2M协议,EDP协议,MQTT协议,HTTP协议,TCP透传,MODBUS协议,JT/T808协议,RCMP协议 ...
- Java快速入门学习笔记9 | Java语言中的方法
有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...
- 【带着canvas去流浪(11)】Three.js入门学习笔记
[摘要] three.js 入门学习笔记 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 资料推荐及建议 1.官方文档 很详细,但是API部分单独 ...
- RT-Thread Nano入门学习笔记(2)
RT-Thread系列 Keil模拟器 STM32F103上手指南学习笔记 RT-Thread Studio快速上手 RT-Thread Nano入门学习笔记(1) 项目实战:快速打造一个桌面mini ...
- Qt 快速入门学习笔记
Qt 快速入门学习笔记 环境安装 环境配置以及安装 安装包下载地址 1.windows安装 msvc编译器模块需要安装Windows软件开发工具包. MinGW是Windows平台使用GNU工具导入库 ...
- python数据分析入门学习笔记
python数据分析入门学习笔记儿 学习利用python进行数据分析的笔记儿&下星期二内部交流会要讲的内容,一并分享给大家.博主粗心大意,有什么不对的地方欢迎指正~还有许多尚待完善的地方,待我 ...
- 01.Java 编程入门学习笔记20210307
Java 编程入门学习笔记-day01 第0章:编程入门 1.计算机的概述 计算机 = 硬件 + 软件 1.1硬件:冯诺依曼体系 CPU: CPU的衡量标准:速度的计量单位是赫兹(Hz),1Hz相当于 ...
- Javascript入门学习笔记
JS入门学习笔记目录 1.JS简介 2.组成部分 3.特点 4.作用 5.JS三种添加方式 6.变量 7.数据类型 8.检测数据类型 9.逗号运算符 10.算术运算符 11.关系运算符 12.逻辑运算 ...
- dubbo入门学习笔记之入门demo(基于普通maven项目)
注:本笔记接dubbo入门学习笔记之环境准备继续记录; (四)开发服务提供者和消费者并让他们在启动时分别向注册中心注册和订阅服务 需求:订单服务中初始化订单功能需要调用用户服务的获取用户信息的接口(订 ...
- Crypto++入门学习笔记(DES、AES、RSA、SHA-256)
Crypto++入门学习笔记(DES.AES.RSA.SHA-256) 背景(只是个人感想,技术上不对后面的内容构成知识性障碍,可以skip): 最近,基于某些原因和需要,笔者需要去了解一下Crypt ...
最新文章
- npm 发布包填坑指南
- imu与gps之间的时间戳_一个时间戳精度问题,引发了一个MySQL血案
- P3527-[POI2011]MET-Meteors【整体二分,树状数组】
- 生信宝典之傻瓜式 (五) - 文献挖掘查找指定基因调控网络
- postman怎么传session_Day 47: 不搞懂Cookie和session誓不罢休
- 我是京东智能配送机器人,已抵达您的楼下
- Java中的生产消费者问题
- 1. MySQL基础概念及mysql安装,从MySQL获得帮助 及视频下载地址
- Linux中mongodb定时远程备份
- VCL组件DevExpress VCL v21.2 - PDF Viewer、图像列表编辑器升级
- 解决GUT GUI中文乱码问题
- 项目管理之团队与团队精神
- defaultdict用法详解
- .Net深入学习:序列化
- Arduino电位器控制
- 【Unity3D 问题总结】Unity报错提示:Asset database transaction committed twice
- MacOS 中解压缩(unzip)出现 illegal byte sequence 的解决方案
- 支持Mac电脑的五款设计软件,你都装好了么?
- UG许可资源优化解决方案-许可不够用,解决UG盗版,UG许可监控,UG律师函
- Unity3D Shader系列之渲染流水线