小程序 graphql_GraphQL应用程序中的五个常见问题(以及如何解决)-程序员宅基地

技术标签: python  java  编程语言  mysql  数据库  

小程序 graphql

by Sacha Greif

由Sacha Greif

GraphQL应用程序中的五个常见问题(以及如何解决) (Five Common Problems in GraphQL Apps (And How to Fix Them))

了解如何释放GraphQL的强大功能而不会遭受缺点 (Learn to unlock the power of GraphQL without suffering its drawbacks)

GraphQL is all the rage these days, and for good reason: it’s an elegant approach that solves many of the problems associated with traditional REST APIs.

GraphQL如今风靡一时,这是有充分理由的:这是一种优雅的方法,可以解决与传统REST API相关的许多问题。

Yet I’d be lying if I told you that GraphQL doesn’t come with its own set of issues. And if you’re not careful, these issues might not only lead to a bloated codebase, but even to a dramatically slowed-down app.

但是,如果我告诉您GraphQL没有它自己的问题,那我会撒谎。 而且,如果您不小心,这些问题可能不仅会导致代码库过大,甚至会导致应用程序速度大大降低。

I’m talking about problems such as:

我说的是诸如以下的问题:

  • Schema duplication

    模式复制

  • Server/client data mismatch

    服务器/客户端数据不匹配

  • Superfluous database calls

    多余的数据库调用

  • Poor performance

    表现不佳

  • Boilerplate overdose

    样板过量

I’m willing to bet your app suffers from at least one of them. Good news is, none of them are incurable!

我敢打赌,您的应用至少会遭受其中之一的困扰。 好消息是,没有一个是无法治愈的!

For each issue, I’ll describe the problem, and then explain how I’m addressing it inside Vulcan, a React/GraphQL open-source framework I’ve been working on over the past year (you should check it out!). But hopefully, you’ll be able to apply the same strategies to your own codebase whether you use Vulcan or not.

对于每个问题,我将描述问题,然后解释如何在过去一年来一直在致力于的React / GraphQL开源框架Vulcan中解决它(您应该检查一下!)。 但是希望无论您是否使用Vulcan,您都可以将相同的策略应用于自己的代码库。

问题:模式复制 (Problem: Schema Duplication)

One of the first things you realize when coding a GraphQL back-end from scratch is that it involves a lot of similar-but-not-quite-identical code, especially when it comes to schemas.

从头开始编写GraphQL后端时,您会意识到的第一件事是,它涉及许多相似但不完全相同的代码,尤其是涉及架构时。

Namely, you need one schema for your database, and another one for your GraphQL endpoint. Not only is it frustrating to have to write more or less the same thing twice, but you now have two independent sources of truths that you need to constantly keep in sync.

即,您需要一个数据库架构,另一个需要GraphQL端点。 不得不多写或少写两次相同的东西不仅令人沮丧,而且您现在拥有两个独立的真理来源,需要不断保持同步。

解决方案:GraphQL模式生成 (Solution: GraphQL Schema Generation)

A number of solutions to this problem have emerged in the GraphQL ecosystem. For example, PostGraphile generates a GraphQL schema from your PostgreSQL database, and Prisma will also help you generate types for your queries and mutations.

GraphQL生态系统中出现了许多解决此问题的方法。 例如, PostGraphile从PostgreSQL数据库生成GraphQL模式, Prisma也将帮助您生成查询和变异的类型。

I also remember hearing Laney Zamore & Adam Kramer from the GraphQL team describe how they directly generated their GraphQL schema from their PHP type definitions.

我还记得GraphQL团队的Laney Zamore和Adam Kramer讲述了他们如何直接从PHP类型定义中生成GraphQL模式。

For Vulcan, I independently stumbled on a very similar solution. I was using SimpleSchema to describe my schemas as JavaScript objects, and I started simply by converting JavaScript’s String type into a GraphQL String, Number into Int or Float, and so on.

对于Vulcan,我独立地偶然发现了一个非常相似的解决方案。 我当时使用SimpleSchema将模式描述为JavaScript对象,然后开始将JavaScript的String类型转换为GraphQL String ,将Number转换为IntFloat ,等等。

So this JavaScript field:

所以这个JavaScript字段:

title: {  type: String}

Would become this GraphQL field:

将成为此GraphQL字段:

title: String

But of course, a GraphQL schema can also have custom types: User, Comment, Event, and so on.

但是,当然,GraphQL架构也可以具有自定义类型: UserCommentEvent等等。

I didn’t want to add too much magic to the schema generation step, so I came up with field resolvers, a simple way to let you specify these custom types. So that this JavaScript field:

我不想在架构生成步骤中添加太多的魔力,所以我想出了字段解析器 ,这是一种让您指定这些自定义类型的简单方法。 这样这个JavaScript字段:

userId{  type: String,  resolveAs: {    fieldName: 'user',    type: 'User',    resolver: document => {      return Users.findOne(document.userId)    }  }}

Becomes:

成为:

user: User

As you can see, we’re defining the actual resolver function on the field as well, since it’s also directly related to the GraphQL field.

如您所见,由于它也与GraphQL字段直接相关,因此我们也在该字段上定义了实际的解析程序功能。

So whether you use something like PostGraphile or write your own schema generation code, I encourage you to avoid schema duplication in your own app.

因此,无论您使用PostGraphile之类的东西还是编写自己的模式生成代码,我都建议您避免在自己的应用程序中重复模式。

Or of course, you can also use a hosted service such as Graphcool to manage your schema using their dashboard and bypass that issue entirely.

或者,当然,您也可以使用诸如Graphcool之类的托管服务通过其仪表板管理您的架构,并完全绕开该问题。

问题:服务器/客户端数据不匹配 (Problem: Server/Client Data Mismatch)

As we’ve just seen, your database and GraphQL API will have different schemas, which translate into different document shapes.

如我们所见,您的数据库和GraphQL API将具有不同的架构,这些架构将转换为不同的文档形状。

So while a post fresh out of the database will have a userId property, the same post as fetched through your API will instead have a user property.

因此,虽然从数据库中重新post将具有userId属性,但与通过您的API提取的post相同,将具有一个user属性。

This means that getting a post author’s name on the client will look like:

这意味着在客户端上获得帖子作者的姓名将类似于:

const getPostNameClient = post => {  return post.user.name}

But on the server, it’ll be a different story altogether:

但是在服务器上,这将是完全不同的故事:

const getPostNameServer = post => {  const postAuthor = Users.findOne(post.userId)  return postAuthor.name}

This can be a problem anytime you’re trying to share code between client and server to simplify your codebase. And even beyond that, it means you’re missing out on GraphQL’s great approach to data querying on the server.

每当您尝试在客户端和服务器之间共享代码以简化代码库时,这都是一个问题。 甚至除此之外,这意味着您会错过GraphQL在服务器上进行数据查询的出色方法。

I recently felt that pain when trying to build a system to generate weekly newsletters: each newsletter was composed of multiple posts and comments, along with info about their authors; in other words, a perfect use case for GraphQL. But this newsletter generation was happening on the server, meaning I didn’t have a way to query my GraphQL endpoint…

最近,当我尝试构建一个每周新闻通讯系统时,我感到非常痛苦:每个新闻通讯由多个帖子和评论以及有关作者的信息组成; 换句话说,是GraphQL的完美用例。 但是,此新闻通讯正在服务器上发生,这意味着我无法查询GraphQL端点…

解决方案:服务器到服务器的GraphQL查询 (Solution: Server-to-Server GraphQL Queries)

Or did I? It turns out that you can run server-to-server GraphQL queries just fine! Just pass your GraphQL executable schema to the graphql function, along with your GraphQL query:

还是我? 事实证明,您可以运行服务器对服务器的GraphQL查询! 只需将您的GraphQL可执行模式与GraphQL查询一起传递给graphql函数

const result = await graphql(executableSchema, query, {}, context, variables);

In Vulcan, I generalized this pattern into a runQuery helper, and I also added queryOne functions to each collection. These act just like MongoDB’s findOne except they return the document as fetched through the GraphQL API:

在Vulcan中, 我将此模式概括为runQuery helper ,并且还向每个集合添加了queryOne函数。 这些行为与MongoDB的findOne除了它们返回通过GraphQL API获取的文档:

const user = await Users.queryOne(userId, {  fragmentText: `    fragment UserFragment on User {      _id      username      createdAt      posts{        _id        title      }    }  `});

Server-to-server GraphQL queries have helped me drastically simplify my code. It let me refactor my newsletter generation call from a mess of successive database calls and loops to a single GraphQL query:

服务器到服务器的GraphQL查询帮助我极大地简化了代码。 它使我可以从一堆连续的数据库调用和循环中重构我的新闻通讯生成调用,以循环到单个GraphQL查询:

query NewsletterQuery($terms: JSON){  SiteData{    title  }  PostsList(terms: $terms){    _id    title    url    pageUrl    linkUrl    domain    htmlBody    thumbnailUrl    commentsCount    postedAtFormatted    user{      pageUrl      displayName    }    comments(limit: 3){      user{        displayName        avatarUrl        pageUrl      }      htmlBody      postedAt    }  }}

The takeaway here: don’t see GraphQL as just a pure client-server protocol. GraphQL can be used to query data in any situation, including client-to-client with Apollo Link State or even during a static build process with Gatsby.

这里的要点:不要将GraphQL视为纯粹的客户端-服务器协议。 GraphQL可用于在任何情况下查询数据,包括具有Apollo链接状态的客户端到客户端,甚至在使用Gatsby的静态构建过程中。

问题:多余的数据库调用 (Problem: Superfluous Database Calls)

Imagine a list of posts, each of which has a user attached to it. You now want to display 10 of these posts, along with the name of their author.

想象一下一个帖子列表,每个帖子都具有一个用户。 现在,您要显示其中的10条帖子,以及其作者的姓名。

With a typical implementation, this would mean two database calls. One to get the 10 posts, and one to get the 10 users corresponding to these posts.

对于典型的实现,这将意味着两个数据库调用。 一个获得10个帖子,一个获得10个与这些帖子对应的用户。

But what about GraphQL? Assuming our posts have a user field with its own resolver, we still have one initial database call to get the list of posts. But we now have an extra call to fetch each user per resolver, for a total of 11 database calls!

但是GraphQL呢? 假设我们的帖子有一个带有自己的解析程序的user字段,我们仍然有一个初始数据库调用来获取帖子列表。 但是我们现在有一个额外的调用来为每个解析器获取每个用户,总共有11个数据库调用!

Now imagine that each post also has 5 comments, each of which has an author. Our number of calls has now ballooned to:

现在想象每个帖子也有5条评论,每个评论都有一位作者。 我们的电话数量现已激增至:

  • 1 for the list of posts

    帖子列表1
  • 10 for the post authors

    帖子作者10个
  • 10 for each sub-list of 5 comments

    每5则评论的子清单10个
  • 50 for the comment authors

    评论作者50个

For a grand total of 71 database calls to display a single view!

共显示71个数据库调用,以显示单个视图

Nobody wants to have to explain to their boss why the homepage is taking 25 seconds to load. Thankfully, there’s a solution: Dataloader.

没人愿意向老板解释为什么首页要花25秒钟来加载。 幸运的是,有一个解决方案: Dataloader

解决方案:数据加载器 (Solution: Dataloader)

Dataloader will let you batch and cache database calls.

Dataloader使您可以批处理和缓存数据库调用。

  • Batching means that if Dataloader figures out that you’re hitting the same database table multiple times, it’ll batch all calls together. In our example, the 10 post authors’ and 50 comment authors’ calls would all be batched into a single call.

    批处理意味着,如果Dataloader发现您多次访问同一数据库表,则它将所有调用一起批处理。 在我们的示例中,10个帖子作者的呼叫和50个评论作者的呼叫将全部合并为一个呼叫。

  • Caching means that if Dataloader detects that two posts (or a post and a comment) have the same author, it will reuse the user object it already has in memory instead of making a new database call.

    缓存意味着,如果Dataloader检测到两个帖子(或一个帖子和一个评论)具有相同的作者,它将重用它已经在内存中拥有的用户对象,而不是进行新的数据库调用。

In practice you don’t always achieve perfect caching and batching, but Dataloader is still a huge help.

在实践中,您并不总是能够实现完美的缓存和批处理,但是Dataloader仍然是一个巨大的帮助。

And Vulcan makes using Dataloader extra easy. Out of the box, every Vulcan model includes Dataloader functions as alternatives to the “normal” MongoDB query functions.

Vulcan使得使用Dataloader变得异常容易。 开箱即用, 每个Vulcan模型都包含Dataloader函数,以替代“常规” MongoDB查询函数。

So in addition to collection.findOne and collection.find, you can use collection.loader.load and collection.loader.loadMany.

因此,除了collection.findOnecollection.find ,您还可以使用collection.loader.loadcollection.loader.loadMany

The one limitation is that Dataloader only works when querying using document IDs. So you can use it to query for a document whose ID is already known, but you’ll still need to hit your database if you want to ask for, say, the most recently created post.

一个限制是Dataloader仅在使用文档ID查询时才起作用。 因此,您可以使用它来查询ID已知的文档,但是如果您要查询(例如)最近创建的帖子,仍然需要打数据库。

问题:性能不佳 (Problem: Poor Performance)

Even with Dataloader enabled, complex views can still trigger multiple database calls, which in turn can make for slow loading times.

即使启用了Dataloader,复杂的视图仍然可以触发多个数据库调用,这又会导致加载时间变慢。

This can be frustrating: on one hand you want to take full advantage of GraphQL’s graph traversal features (“show me the authors of the comments of the author of the post of…” etc.). But on the other hand, you don’t want your app to become slow and unresponsive.

这可能令人沮丧:一方面,您想充分利用GraphQL的图遍历功能(例如,“向我显示...帖子作者的评论的作者”等)。 但另一方面,您不希望您的应用变慢且无响应。

解决方案:查询缓存 (Solution: Query Caching)

There is a solution though, which is to cache the entire GraphQL query response. Unlike Dataloader, whose scope is limited to the current request (meaning it will only cache documents within the same query), I’m talking here about caching the whole query for a period of time.

但是,有一个解决方案,就是缓存整个 GraphQL查询响应。 不同的DataLoader,其范围仅限于当前请求(这意味着它只会缓存在同一查询的文件),我在这里谈论缓存整个查询一段时间。

Apollo Engine is a great way to do just that. It’s a hosted service that provides analytics about your GraphQL queries, but it also has a very useful caching feature.

Apollo Engine是做到这一点的好方法。 它是一项托管服务,可提供有关GraphQL查询的分析,但它也具有非常有用的缓存功能

Vulcan comes with a built-in Engine integration, you just need to add your API key to your settings file. You can then add the enableCache: true argument to your GraphQL queries to cache them using Engine.

Vulcan带有内置的Engine集成,您只需要将API密钥添加到设置文件中即可。 然后,您可以向您的GraphQL查询添加enableCache: true参数,以使用Engine对其进行缓存。

Or, using Vulcan’s built-in data-loading higher-order components:

或者,使用Vulcan的内置数据加载高阶组件

withList({  collection: Posts,   enableCache: true})(PostsList)

The beauty of this approach is that you can easily control which queries are cached and which aren’t, even for the same resolver. For example, you might want to cache the list of recent posts featured on your frequently-accessed homepage, but not the full list of posts available on your archives page.

这种方法的优点在于,即使对于同一个解析器,您也可以轻松控制哪些查询被缓存,哪些不缓存。 例如,您可能想缓存您经常访问的主页上的最新帖子列表,而不是存档页面上可用帖子的完整列表。

A final note: caching might not always be possible. For example, it’s not advisable for data that changes frequently, or for data that depends on the currently logged-in user.

最后一点:并非总是可能进行缓存。 例如,对于频繁更改的数据或依赖于当前登录用户的数据,建议不要这样做。

问题:样板过量 (Problem: Boilerplate Overdose)

This is by no means an issue exclusive to GraphQL apps, but it’s true that they generally require you to write a lot of similar boilerplate code.

这绝不是GraphQL应用程序独有的问题,但确实,它们通常需要您编写很多类似的样板代码。

Typically, adding a new model (e.g. Comments) to your app will involve the following steps:

通常,向您的应用添加新模型(例如Comments )将涉及以下步骤:

  • Writing a resolver to get a list of comments.

    编写解析器以获取评论列表。
  • Writing a higher-order component (a.k.a. container) to load that list of comments.

    编写一个高阶组件(也称为容器)以加载该评论列表。
  • Optionally, writing a resolver to get a single comment by ID or slug along with the corresponding higher-order component.

    (可选)编写解析器以获取ID或Slug以及相应的高阶组件的单个注释。
  • Writing mutations for inserting a new comment, editing a comment, and deleting a comment.

    编写用于插入新注释,编辑注释和删除注释的变体。
  • Adding the corresponding forms and form-handling code.

    添加相应的表单和表单处理代码。

That’s a whole lot of CRUD!

这是很多CRUD!

解决方案:通用解析器,突变和高阶组件 (Solution: Generic Resolvers, Mutations, and Higher-Order Components)

Vulcan’s approach is to give you smart, easy-to-use generic options for each of these. You’ll get:

Vulcan的方法是为每个选项提供智能,易于使用的通用选项。 你会得到:

These are all written in a generic enough way that they’ll work with any new model out of the box.

这些都以足够通用的方式编写,可以与任何新模型一起使用。

To be sure, this “one-size-fits-all” approach is not without its downsides. For example, because queries are generated dynamically by the generic higher-order components, it’s a bit harder to use static queries.

可以肯定的是,这种“千篇一律”的方法并非没有缺点。 例如,由于查询是由通用的高阶组件动态生成的,因此使用静态查询会有点困难。

But this strategy is still a great way to get started, at least until you have time to refactor each part of your app to a more tailor-made solution.

但是,至少在您有时间将应用程序的每个部分重构为更量身定制的解决方案之前,这种策略仍然是入门的好方法。

GraphQL is still relatively new, which means that while everybody is busy extolling its virtues, it’s easy to overlook the very real challenges involved with building GraphQL apps.

GraphQL还是一个​​相对较新的概念,这意味着尽管每个人都在忙于赞美自己的优点,但很容易忽略构建GraphQL应用程序所涉及的真正挑战。

Thankfully these challenges all have solutions, and the more we discuss them (the Vulcan Slack is a great place for that by the way!), the better these solutions will become!

值得庆幸的是,这些挑战都有解决方案,而我们讨论的内容越多(顺便说一下, Vulcan Slack是一个绝佳的选择!),这些解决方案将变得更好!

翻译自: https://www.freecodecamp.org/news/five-common-problems-in-graphql-apps-and-how-to-fix-them-ac74d37a293c/

小程序 graphql

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cumi7754/article/details/108159302

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签