热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【转载】RESTfulAPI设计最佳实践

英文原文:http:www.vinaysahni.combest-practices-for-a-pragmatic-restful-api转载原文:http:ww

英文原文:http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
转载原文:http://www.oschina.net/translate/best-practices-for-a-pragmatic-restful-api?p=1
翻译作者:wei, 优雅先生, jingxing05, super0555, 伪猫, 赵亮-碧海情天, Khiyuan,
maverickpuss, out_lier, 多多de棉花糖

数据模型已经稳定,接下来你可能需要为web(网站)应用创建一个公开的API(应用程序编程接口)。需要认识到这样一个问题:一旦API发布后,就很难对它做很大的改动并且保持像先前一样的正确性。现在,网络上有很多关于API设计的思路。但是在全部案例中没有一种被广泛采纳的标准,有很多的选择:你接受什么样的格式?如何认证?API应该被版本化吗?

在为SupportFu(一个轻量级的Zendesk替换实现)设计API时,对于这些问题我尽量得出一些务实的答案。我的目标是设计这样一个API,它容易使用和采纳,足够灵活去为我们用户接口去埋单。


API的关键要求

许多网上能找到的API设计观点都是些学术讨论,这些讨论是关于模糊标准的主观解释,而不是关于在现实世界中具有意义的事。本文中我的目标是,描述一下为当今的web应用而设计的实用的API的最佳实践。如果感觉不对,我不会去尝试满足某个标准。为了帮助进行决策,我已经写下了API必须力争满足的一些要求:

它应当在需要的地方使用 web 标准
它应当对开发者友好并且便于在浏览器地址栏中浏览和探索
它应当是简单、直观和一致的,使它用起来方便和舒适
它应当提供足够的灵活性来增强大多数的 SupportFu 用户界面
它应当是高效的,同时要维持和其他需求之间的平衡

一个 API 是一个开发者的 UI - 就像其他任何 UI 一样, 确保用户体验被认真的考虑过是很重要的!


使用 RESTful URLs and actions

如果有一样东西获得广泛认可的话,那就是 RESTful 原则。Roy Felding 在他论文 network based software architectures 的 第五章 中首次介绍了这些原则。

这些REST的关键原则与将你的 API 分割成逻辑资源紧密相关。使用HTTP请求控制这些资源,其中,这些方法(GET, POST, PUT, PATCH, DELETE)具有特殊含义。

可是我该整出什么样的资源呢?好吧,它们应该是有意义于 API 使用者的名词(不是动词)。虽然内部Model可以简单地映射到资源上,但那不一定是个一对一的映射。这里的关键是不要泄漏与API不相关的实现细节。一些相关的名词可以是 票,用户和小组。

一旦定义好了资源, 需要确定什么样的 actions 应用它们,这些 actions 怎么映射到你的 API 上。RESTful 原则提供了 HTTP methods 映射作为策略来处理 CRUD actions,如下:

GET /tickets - 获取 tickets 列表
GET /tickets/12 - 获取一个单独的 ticket
POST /tickets - 创建一个新的 ticket
PUT /tickets/12 - 更新 ticket #12
PATCH /tickets/12 - 部分更新 ticket #12
DELETE /tickets/12 - 删除 ticket #12

REST 非常棒的是,利用现有的 HTTP 方法在单个的 /tickets 接入点上实现了显著的功能。没有什么方法命名约定需要去遵循,URL 结构是整洁干净的。 REST 太棒了!
接入点的名称应该选择单数还是复数呢?keep-it-simple原则可以在此应用。虽然你内在的语法知识会告诉你用复数形式描述单一资源实例是错误的,但实用主义的答案是保持URL格式一致并且始终使用复数形式。不用处理各种奇形怪状的复数形式(比如person/people,goose/geese)可以让API消费者的生活更加美好,也让API提供者更容易实现API(因为大多数现代框架天然地将/tickets和/tickets/12放在同一个控制器下处理)。

但是你该如何处理(资源的)关系呢?如果关系依托于另外一个资源,Restful原则提供了很好的指导原则。让我们来看一个例子。SupportFu的一个ticket包含许多消息(message)。这些消息逻辑上与/tickets接入点的映射关系如下:

GET /tickets/12/messages - 获取ticket #12下的消息列表
GET /tickets/12/messages/5 - 获取ticket #12下的编号为5的消息
POST /tickets/12/messages - 为ticket #12创建一个新消息
PUT /tickets/12/messages/5 - 更新ticket #12下的编号为5的消息
PATCH /tickets/12/messages/5 - 部分更新ticket #12下的编号为5的消息
DELETE /tickets/12/messages/5 - 删除ticket #12下的编号为5的消息

或者如果某种关系不依赖于资源,那么在资源的输出表示中只包含一个标识符是有意义的。API消费者然后除了请求资源所在的接入点外,还得再请求一次关系所在的接入点。但是如果一般情况关系和资源一起被请求,API可以提供自动嵌套关系表示到资源表示中,这样可以防止两次请求API。

如果Action不符合CRUD操作那该怎么办?

这是一个可能让人感到模糊不解的地方。有几种处理方法:

重新构造这个Action,使得它像一个资源的field(我理解为部分域或者部分字段)。这种方法在Action不包含参数的情况下可以奏效。例如一个有效的action可以映射成布尔类型field,并且可以通过PATCH更新资源。
利用RESTful原则像处理子资源一样处理它。例如,Github的API让你通过PUT /gists/:id/star 来 star a gist ,而通过DELETE /gists/:id/star来进行 unstar 。
有时候你实在是没有办法将Action映射到任何有意义的RESTful结构。例如,多资源搜索没办法真正地映射到任何一个资源接入点。这种情况,/search 将非常有意义,虽然它不是一个名词。这样做没有问题 - 你只需要从API消费者的角度做正确的事,并确保所做的一切都用文档清晰记录下来了以避免(API消费者的)困惑。

总是使用 SSL

总是使用SSL,没有例外。今天,您的web api可以从任何地方访问互联网(如图书馆、咖啡店、机场等)。不是所有这些都是安全的,许多不加密通信,便于窃听或伪造,如果身份验证凭证被劫持。

另一个优点是,保证总是使用SSL加密通信简化了认证效果——你可以摆脱简单的访问令牌,而不是让每个API请求签署。

要注意的一点是非SSL访问API URLs。不要重定向这些到对应的SSL。相反,抛出一个系统错误!最后一件你想要的是配置不佳的客户发送请求到一个未加密的端点,只是默默地重定向到实际加密的端点。


文档

API的好坏关键看其文档的好坏. 好的API的说明文档应该很容易就被找到,并能公开访问。在尝试任何整合工作前大部分开发者会先查看其文档。当文档被藏于一个PDF之中或要求必须登记信息时,将很难被找到也很难搜索到。

好的文档须提供从请求到响应整个循环的示例。最好的是,请求应该是可粘贴的例子,要么是可以贴到浏览器的链接,要么是可以贴到终端里的curl示例 。 GitHub 和 Stripe 在这方面做的非常出色。

一旦你发布一个公开的API,你必须承诺”在没有通告的前提下,不会更改APIDe功能” .对于外部可见API的更新,文档必须包含任何将废弃的API的时间表和详情。应该通过博客(更新日志)或者邮件列表送达更新说明(最好两者都通知)。


版本控制

必须对API进行版本控制。版本控制可以快速迭代并避免无效的请求访问已更新的接入点。它也有助于帮助平滑过渡任何大范围的API版本变迁,这样就可以继续支持旧版本API。

关于API的版本是否应该包含在URL或者请求头中 莫衷一是。从学术派的角度来讲,它应该出现在请求头中。然而版本信息出现在URL中必须保证不同版本资源的浏览器可浏览性(browser explorability),还记得文章开始提到的API要求吗?

我非常赞成 approach that Stripe has taken to API versioning - URL包含一个主版本号(比如http://shonzilla/api/v1/customers/1234)
),但是API还包含基于日期的子版本(比如http://shonzilla/api/v1.2/customers/1234),可以通过配置HTTP请求头来进行选择。这种情况下,主版本确保API结构总体稳定性,而子版本会考虑细微的变化(field deprecation、接入点变化等)。

API不可能完全稳定。变更不可避免,重要的是变更是如何被控制的。维护良好的文档、公布未来数月的deprecation计划,这些对于很多API来说都是一些可行的举措。它归根结底是看对于业界和API的潜在消费者是否合理。


结果过滤,排序和搜索

最好是尽量保持基本资源URL的简洁性。 复杂结果过滤器、排序需求和高级搜索 (当限定在单一类型的资源时) ,都能够作为在基本URL之上的查询参数来轻松实现。下面让我们更详细的看一下:

过滤: 对每一个字段使用一个唯一查询参数,就可以实现过滤。 例如,当通过“/tickets”终端来请求一个票据列表时,你可能想要限定只要那些在售的票。这可以通过一个像 GET /tickets?state=open 这样的请求来实现。这里“state”是一个实现了过滤功能的查询参数。

排序: 跟过滤类似, 一个泛型参数排序可以被用来描述排序的规则. 为适应复杂排序需求,让排序参数采取逗号分隔的字段列表的形式,每一个字段前都可能有一个负号来表示按降序排序。我们看几个例子:

GET /tickets?sort=-priority - 获取票据列表,按优先级字段降序排序
GET /tickets?sort=-priority,created_at - 获取票据列表,按“priority”字段降序排序。在一个特定的优先级内,较早的票排在前面。

搜索: 有时基本的过滤不能满足需求,这时你就需要全文检索的力量。或许你已经在使用 ElasticSearch 或者其它基于 Lucene 的搜索技术。当全文检索被用作获取某种特定资源的资源实例的机制时, 它可以被暴露在API中,作为资源终端的查询参数,我们叫它“q”。搜索类查询应当被直接交给搜索引擎,并且API的产出物应当具有同样的格式,以一个普通列表作为结果。

把这些组合在一起,我们可以创建以下一些查询:

GET /tickets?sort=-updated_at - 获取最近更新的票
GET /tickets?state=closed&sort=-updated_at - 获取最近更新并且状态为关闭的票。
GET /tickets?q=return&state=open&sort=-priority,created_at - 获取优先级最高、最先创建的、状态为开放的票,并且票上有 'return' 字样。

一般查询的别名

为了使普通用户的API使用体验更加愉快, 考虑把条件集合包装进容易访问的RESTful 路径中。比如上面的,最近关闭的票的查询可以被包装成 GET /tickets/recently_closed


限制哪些字段由API返回

API的使用者并不总是需要一个资源的完整表示。选择返回字段的功能由来已久,它使得API使用者能够最小化网络阻塞,并加速他们对API的调用。

使用一个字段查询参数,它包含一个用逗号隔开的字段列表。例如,下列请求获得的信息将刚刚足够展示一个在售票的有序列表:

GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at


更新和创建应该返回一个资源描述

一个 PUT, POST 或者 PATCH 调用可能会对指定资源的某些字段造成更改,而这些字段本不在提供的参数之列 (例如: created_at 或 updated_at 这两个时间戳)。 为了防止API使用者为了获取更新后的资源而再次调用该API,应当使API把更新(或创建)后的资源作为response的一部分来返回。

以一个产生创建活动的 POST 操作为例, 使用一个 HTTP 201 状态代码 然后包含一个 Location header 来指向新生资源的URL。


你是否应该HATEOAS?

(译注:Hypermedia as the Engine of Application State (HATEOAS)超媒体作为应用程序状态引擎)

对于API消费方是否应该创建链接,或者是否应该将链接提供给API,有许多混杂的观点。RESTful的设计原则指定了HATEOAS ,大致说明了与某个端点的交互应该定义在元数据(metadata)之中,这个元数据与输出结果一同到达,并不基于其他地方的信息。

虽然web逐渐依照HATEOAS类型的原则运作(我们打开一个网站首页并随着我们看到的页面中的链接浏览),我不认为我们已经准备好API的HATEOAS了。当浏览一个网站的时候,决定点击哪个链接是运行时做出的。然而,对于API,决定哪个请求被发送是在写API集成代码时做出的,并不是运行时。这个决定可以移交到运行时吗?当然可以,不过顺着这条路没有太多好处,因为代码仍然不能不中断的处理重大的API变化。也就是说,我认为HATEOAS做出了承诺,但是还没有准备好迎接它的黄金时间。为了完全实现它的潜能,需要付出更多的努力去定义围绕着这些原则的标准和工具。

目前而言,最好假定用户已经访问过输出结果中的文档&包含资源标识符,而这些API消费方会在制作链接的时候用到。关注标识符有几个优势——网络中的数据流减少了,API消费方存储的数据也减少了(因为它们存储的是小的标识符而不是包含标识符的URLs)。

同样的,在URL中提供本文倡导的版本号,对于在一个很长时间内API消费方存储资源标识符(而不是URLs),它更有意义。总之,标识符相对版本是稳定的,但是表示这一点的URL却不是的!


只返回JSON

是时候在API中丢弃XML了。XML冗长,难以解析,很难读,他的数据模型和大部分编程语言的数据模型 不兼容,而他的可扩展性优势在你的主要需求是必须序列化一个内部数据进行输出展示时变得不相干。

我不打算对上述进行解释了,貌似诸如 (YouTube, Twitter 和 Box)之类的已经开始了去XML化.

给你一张google趋势图,比较XML API 和 JSON API的,供你参考:

但是,如果你的客户群包括大量的企业客户,你会发现自己不得不支持XML的方式。如果你必须这样,一个新问题出现了:

媒体类型是应该基于Accept头还是基于URL呢 ? 为确保浏览器的浏览性,应该基于URL。这里最明智的选择是在端点URL后面附加 .json 或 .xml 的扩展.


字段名称书写格式的 snake_case vs camelCase

如果你在使用JSON (Javascript Object Notation) 作为你的主要表示格式,正确的方法就是遵守Javascript命名约定——对字段名称使用camelCase!如果你要走用各种语言建设客户端库的路线,最好使用它们惯用的命名约定—— C# & Java 使用camelCase, python & ruby 使用snake_case。

深思:我一直认为snake_case比Javascript的camelCase约定更容易阅读。我没有任何证据来支持我的直觉,直到现在,基于从2010年的camelCase 和 snake_case的眼动追踪研究 (PDF),snake_case比驼峰更容易阅读20%!这种阅读上的影响会影响API的可勘探性和文档中的示例。

许多流行的JSON API使用snake_case。我怀疑这是由于序列化库遵从它们所使用的底层语言的命名约定。也许我们需要有JSON序列库来处理命名约定转换


缺省情况下确保漂亮的打印和支持gzip

一个提供空白符压缩输出的API,从浏览器中查看结果并不美观。虽然一些有序的查询参数(如 ?pretty=true )可以提供来使漂亮打印生效,一个默认情况下能进行漂亮打印的API更为平易近人。额外数据传输的成本是微不足道的,尤其是当你比较不执行gzip压缩的成本。

考虑一些用例:假设分析一个API消费者正在调试并且有自己的代码来打印出从API收到的数据——默认情况下这应是可读的。或者,如果消费者抓住他们的代码生成的URL,并直接从浏览器访问它——默认情况下这应是可读的。这些都是小事情。做好小事情会使一个API能被更愉快地使用!


速率限制

为了防止滥用,标准的做法是给API增加某种类型的速率限制。RFC 6585 中介绍了一个HTTP状态码429 请求过多来实现这一点。

不论怎样,在用户实际受到限制之前告知他们限制的存在是很有用的。这是一个现在还缺乏标准的领域,但是已经有了一些流行的使用HTTP响应头信息的惯用方法。

最少时包含下列头信息(使用Twitter的命名约定 来作为头信息,通常没有中间词的大写):

X-Rate-Limit-Limit - 当期允许请求的次数
X-Rate-Limit-Remaining - 当期剩余的请求次数
X-Rate-Limit-Reset - 当期剩余的秒数

为什么对X-Rate-Limit-Reset不使用时间戳而使用秒数?

一个时间戳包含了各种各样的信息,比如日期和时区,但它们却不是必需的。一个API使用者其实只是想知道什么时候能再次发起请求,对他们来说一个秒数用最小的额外处理回答了这个问题。同时规避了时钟偏差的问题。

有些API给X-Rate-Limit-Reset使用UNIX时间戳(纪元以来的秒数)。不要这样做!

为什么对X-Rate-Limit-Reset使用UNIX时间戳是不好的做法?

HTTP 规范已经指定使用RFC 1123 的日期格式 (目前被使用在日期, If-Modified-Since & Last-Modified HTTP头信息中)。如果我们打算指定一种使用某种形式时间戳的、新的HTTP头信息,我们应当遵循RFC 1123规定,而不是使用UNIX时间戳。


认证

一个 RESTful API 应当是无状态的。这意味着认证请求应当不依赖于COOKIE或session。相反,每一个请求都应当携带某种类型的认证凭证。

由于总是使用SSL,认证凭证能够被简化为一个随机产生的访问令牌,里面传入一个使用HTTP Basic Auth的用户名字段。这样做的极大的好处是,它是完全的浏览器可探测的 - 如果浏览器从服务器收到一个401未授权状态码,它仅需要一个弹出框来索要凭证即可。

然而,这种基于基本认证的令牌的认证方法,仅在满足下列情形时才可用,即用户可以把令牌从一个管理接口复制到API使用者环境。当这种情形不能成立时,应当使用OAuth 2来产生安全令牌并传递给第三方。OAuth 2使用了承载令牌(Bearer tokens) 并且依赖于SSL的底层传输加密。

一个需要支持JSONP的API将需要第三种认证方法,因为JSONP请求不能发送HTTP基本认证凭据(HTTP Basic Auth)或承载令牌(Bearer tokens) 。这种情况下,可以使用一个特殊的查询参数access_token。注意,使用查询参数token存在着一个固有的安全问题,即大多数的web服务器都会把查询参数记录到服务日志中。

这是值得的,所有上面三种方法都只是跨API边界两端的传递令牌的方式。实际的底层令牌本身可能都是相同的。


错误

就像一个HTML错误页面给访问者展示了有用的错误信息一样,一个API应当以一种已知的可使用的格式来提供有用的错误信息。 错误的表示形式应当和其它任何资源没有区别,只是有一套自己的字段。

API应当总是返回有意义的HTTP状态代码。API错误通常被分成两种类型: 代表客户端问题的400系列状态码和代表服务器问题的500系列状态码。最简情况下,API应当把便于使用的JSON格式作为400系列错误的标准化表示。如果可能(意思是,如果负载均衡和反向代理能创建自定义的错误实体), 这也适用于500系列错误代码。

一个JSON格式的错误信息体应当为开发者提供几样东西 - 一个有用的错误信息,一个唯一的错误代码 (能够用来在文档中查询详细的错误信息) 和可能的详细描述。这样一个JSON格式的输出可能会像下面这样:

{
"code" : 1234,
"message" : "Something bad happened :(",
"description" : "More details about the error here"
}

对PUT, PATCH和POST请求进行错误验证将需要一个字段分解。下面可能是最好的模式:使用一个固定的顶层错误代码来验证错误,并在额外的字段中提供详细错误信息,就像这样:

{
"code" : 1024,
"message" : "Validation Failed",
"errors" : [
{
"code" : 5432,
"field" : "first_name",
"message" : "First name cannot have fancy characters"
},
{
"code" : 5622,
"field" : "password",
"message" : "Password cannot be blank"
}
]
}

HTTP 状态代码

HTTP定义了一套可以从API返回的有意义的状态代码。 这些代码能够用来帮助API使用者对不同的响应做出相应处理。我已经把你必然会用到的那些列成了一个简短的清单:

200 OK (成功) - 对一次成功的GET, PUT, PATCH 或 DELETE的响应。也能够用于一次未产生创建活动的POST
201 Created (已创建) - 对一次导致创建活动的POST的响应。 同时结合使用一个位置头信息指向新资源的位置- Response to a POST that results in a creation. Should be combined with a Location header pointing to the location of the new resource
204 No Content (没有内容) - 对一次没有返回主体信息(像一次DELETE请求)的请求的响应
304 Not Modified (未修改) - 当使用HTTP缓存头信息时使用304
400 Bad Request (错误的请求) - 请求是畸形的, 比如无法解析请求体
401 Unauthorized (未授权) - 当没有提供或提供了无效认证细节时。如果从浏览器使用API,也可以用来触发弹出一次认证请求
403 Forbidden (禁止访问) - 当认证成功但是认证用户无权访问该资源时
404 Not Found (未找到) - 当一个不存在的资源被请求时
405 Method Not Allowed (方法被禁止) - 当一个对认证用户禁止的HTTP方法被请求时
410 Gone (已删除) - 表示资源在终端不再可用。当访问老版本API时,作为一个通用响应很有用
415 Unsupported Media Type (不支持的媒体类型) - 如果请求中包含了不正确的内容类型
422 Unprocessable Entity (无法处理的实体) - 出现验证错误时使用
429 Too Many Requests (请求过多) - 当请求由于访问速率限制而被拒绝时

推荐阅读
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了markdown[软件代理设置]相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 关于extjs开发实战pdf的信息
    本文目录一览:1、extjs实用开发指南2、本 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 2022年的风口:你看不起的行业,真的很挣钱!
    本文介绍了2022年的风口,探讨了一份稳定的副业收入对于普通人增加收入的重要性,以及如何抓住风口来实现赚钱的目标。文章指出,拼命工作并不一定能让人有钱,而是需要顺应时代的方向。 ... [详细]
  • 从零基础到精通的前台学习路线
    随着互联网的发展,前台开发工程师成为市场上非常抢手的人才。本文介绍了从零基础到精通前台开发的学习路线,包括学习HTML、CSS、JavaScript等基础知识和常用工具的使用。通过循序渐进的学习,可以掌握前台开发的基本技能,并有能力找到一份月薪8000以上的工作。 ... [详细]
  • ElasticSerach初探第一篇认识ES+环境搭建+简单MySQL数据同步+SpringBoot整合ES
    一、认识ElasticSearch是一个基于Lucene的开源搜索引擎,通过简单的RESTfulAPI来隐藏Lucene的复杂性。全文搜索,分析系统&# ... [详细]
  • Hello.js 是一个用于连接OAuth2服务的JavascriptRESTFULAPI库,如Go ... [详细]
author-avatar
别喷我我还小_216
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有