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

rtemsapi用户指南_基本的ElixirApi指南

rtemsapi用户指南Elixir代表了相对较新的编程语言,面向更广泛的受众。它于2011年发布,此后一直在开发中。他的主要特征是取消功能范式

rtems api用户指南

Elixir代表了相对较新的编程语言,面向更广泛的受众。 它于2011年发布,此后一直在开发中。 他的主要特征是取消功能范式,因为它建立在Erlang之上并在BEAM(Erlang VM)上运行。

Elixir专为构建快速,可扩展和可维护的应用程序而设计,而使用Phoenix可以在Web环境中开发这些应用程序。 Phoenix是用Elixir编写的Web框架,它从流行的框架(例如Python的Django或Ruby on Rails)中汲取了很多概念。 如果您熟悉这些,那将是一个不错的起点。

文献资料

Elixir / Phoenix是很好的组合,但是在开始编写应用程序之前,那些不熟悉所有概念的人应该首先阅读以下文档。

  • Elixir-详细文档,从Elixir基本类型到混合和OTP等高级内容,
  • 建议使用Dave Thomas 编写的Elixir ,
  • ExUnit-用于测试的内置框架,
  • Phoenix -Phoenix框架文档,包括所有概念,并带有示例和
  • Ecto -Elixir的ORM的文档和API。

设置应用

Elixir随附Mix,它是内置工具,可帮助编译,生成和测试应用程序,获取依赖项等。

我们通过运行来创建我们的应用程序

mix phx.new company_api

这告诉mix创建名为company_api的新Phenix应用。 运行此指令后,将创建应用程序结构:

bash
* creating company_api/config/config.exs
* creating company_api/config/dev.exs
* creating company_api/config/prod.exs
* creating company_api/config/prod.secret.exs
* creating company_api/config/test.exs
* creating company_api/lib / company_api / application . ex
* creating company_api/ lib / company_api . ex
* creating company_api/ lib / company_api_web / channels / user_socket . ex
* creating company_api/ lib / company_api_web / views / error_helpers . ex
* creating company_api/ lib / company_api_web / views / error_view . ex
* creating company_api/ lib / company_api_web / endpoint . ex
* creating company_api/ lib / company_api_web / router . ex
* creating company_api/ lib / company_api_web . ex
* creating company_api/mix.exs
* creating company_api/README.md
* creating company_api/test/support/channel_case.ex
* creating company_api/test/support/conn_case.ex
* creating company_api/test/test_helper.exs
* creating company_api/test/company_api_web/views/error_view_test.exs
* creating company_api/ lib / company_api_web / gettext . ex
* creating company_api/priv/gettext/en/LC_MESSAGES/errors.po
* creating company_api/priv/gettext/errors.pot
* creating company_api/ lib / company_api / repo . ex
* creating company_api/priv/repo/seeds.exs
* creating company_api/test/support/data_case.ex
* creating company_api/ lib / company_api_web / controllers / page_controller . ex
* creating company_api/ lib / company_api_web / templates / layout / app . html . eex
* creating company_api/ lib / company_api_web / templates / page / index . html . eex
* creating company_api/ lib / company_api_web / views / layout_view . ex
* creating company_api/ lib / company_api_web / views / page_view . ex
* creating company_api/test/company_api_web/controllers/page_controller_test.exs
* creating company_api/test/company_api_web/views/layout_view_test.exs
* creating company_api/test/company_api_web/views/page_view_test.exs
* creating company_api/.gitignore
* creating company_api/assets/brunch-config.js
* creating company_api/assets/css/app.css
* creating company_api/assets/css/phoenix.css
* creating company_api/assets/js/app.js
* creating company_api/assets/js/socket.js
* creating company_api/assets/package.json
* creating company_api/assets/static/robots.txt
* creating company_api/assets/static/images/phoenix.png
* creating company_api/assets/static/favicon.ico

如果出现提示,请安装其他依赖项。 接下来,我们需要配置数据库。 在此示例中,我们使用了PostgreSQL,通常Phoenix与该DBMS的集成最佳。

打开/config/dev.exs/config/test.exs并设置用户名,密码和数据库名称。 设置数据库后,运行

mix ecto .create

这将创建开发和测试数据库,之后

mix phx.server

这应该在默认端口4000上启动服务器(Cowboy)。在浏览器中进行检查,如果看到的是登陆页面,则设置很好。 所有配置都放在/config/config.exs文件中。

创建API

在编码之前,将解释开发的几个部分:

  • 使用ExUnit编写测试,测试模型和控制器,
  • 编写迁移,
  • 编写模型
  • 编写控制器,
  • 路由,
  • 撰写意见,
  • 使用Guardian和
  • 频道。

请注意,以下部分不会针对整个应用程序进行描述,但是您会明白的。

测试和编写模型

在开发过程中,我们想编写干净的代码,并且还要考虑规范以及代码在实现之前需要做什么。 这就是为什么我们使用TDD方法。

首先在目录test / company_api_web /中创建模型目录,然后创建user_test.exs。 之后,创建一个模块:

defmodule CompanyApiWeb.UserTest douse CompanyApi.DataCase, async: true
end

在第二行中,我们使用宏用法注入一些外部代码,在这种情况下,将data_case.exs脚本放置在test / support /目录中以及其他脚本中,并使用`async: true`来表示该测试将与其他测试异步运行。 但是要小心,如果测试将数据写入数据库或在某种意义上更改了某些数据,则它不应运行asyc。

想想应该测试什么。 在这种情况下,让我们测试使用有效和无效数据创建用户。 可以通过模块属性将某些模拟数据设置为常量,例如:

@valid_attributes %{ name: "John" ,subname: "Doe" ,email: "doe@gmail.com" ,job: "engineer"}

当然,您不必使用模块属性,但这可以使代码更简洁。 接下来让我们编写测试。

test"user with valid attributes" douser = CompanyApiWeb.User.reg_changeset(%User{}, @valid_attributes )assert user.valid?
end

在此测试中,我们尝试通过调用方法reg_changeset / 2并声明为真值来创建变更集 。

如果我们用

mixtest test /company_api_web/models/user_test.exs

考试当然会失败。 首先,我们甚至没有用户模块,但是我们甚至没有数据库中的用户表。

接下来,我们需要编写一个迁移。

mix ecto.gen .migration create_user

生成priv / repo / migrations /中的迁移 。 在那里,我们使用Sugar Elixir语法定义了用于表创建的函数,然后将其转换为适当SQL查询。

def change docreate table( :users ) doadd :name , :varcharadd :subname , :varcharadd :email , :varcharadd :job , :varcharadd :password , :varchartimestamps()end
end

函数create / 2从函数table / 2返回的结构中创建数据库表。 有关字段类型,选项和创建索引的详细信息,请阅读docs。 默认情况下,将为每个表生成代理键,名称为id,类型为整数(如果未另外定义)。

现在我们运行命令

mix ecto .migrate

运行迁移。 接下来,我们需要创建模型,因此在lib / company_api_web /中创建models目录,并创建user.ex文件。 我们的模型用于表示数据库表中的数据,因为它将数据映射到Elixir结构中。

defmodule CompanyApiWeb.User douse CompanyApiWeb, :modelschema "users" dofield :name , :stringfield :subname , :stringfield :email , :stringfield :password , :stringfield :job , :stringend def reg_changeset (changeset, params \\ %{}) dochangeset|> cast(params, [ :name , :subname , :email , :job , :password ])|> validate_required([ :name , :subname , :email , :job ])|> validate_format( :email , ~r/\S+@\S+\.\S+ /)end
end

在第2行,我们使用lib / company_api_web / company_api_web.ex中定义的帮助程序,该帮助程序实际上会导入所有必要的模块以创建模型。 如果打开文件,您会看到模型实际上是一个函数,与控制器,视图,通道,路由器等相同。(如果没有模型函数,则可以自己添加)。

两种重要的方法是模式&#xff08;表<->结构映射&#xff09;和changeset / 2 。 Changeset函数不是必需的&#xff0c;但是Elixir的方法就是创建修改数据库的结构。 我们可以定义一个用于注册&#xff0c;登录等。所有验证和关联检查都可以在我们尝试将数据插入数据库之前完成。

有关更多详细信息&#xff0c;请查看Ecto.Changeset文档。 如果我们现在再次运行测试&#xff0c;它将通过。 根据需要添加任意数量的测试用例&#xff0c;并尝试覆盖所有边缘用例。

这应该包含简单模型的创建。 添加关联将在前面提到。

测试和编写控制器

测试控制器与测试模型同等重要。 我们将测试新用户的注册&#xff0c;并让所有注册用户进入系统。 再次&#xff0c;我们在test / company_api_web / controllers /中创建名称为user_controller_test.exs的测试。 通过控制器测试&#xff0c;我们将使用conn_case.exs脚本。 在测试模型时&#xff08;因为我们不需要&#xff09;&#xff0c;测试中没有提到的另一重要事项是设置块。

setupdouser &#61;%User{}|> User.reg_changeset( &#64;user )|> Repo.insert!conn &#61;build_conn()|> put_req_header( "accept" , "application/json" )%{ conn: conn, user: user}
end

在调用每个测试用例之前&#xff0c;将调用Setup块&#xff0c;并且在此块中&#xff0c;我们准备用于测试的数据。 我们可以以元组或映射的形式从块中返回数据。 在此块中&#xff0c;我们将一个用户插入数据库并创建连接结构&#xff08;即连接模型&#xff09;。 同样&#xff0c;常量可用于设置数据。

&#64;valid_data %{ name: "Jim" ,subname: "Doe" ,email: "doe&#64;gmail.com" ,job: "CEO"}
&#64;user %{ name: "John" ,subname: "Doe" ,email: "doe&#64;gmail.com" ,job: "engineer"}
&#64;user_jane %{ name: "Jane" ,subname: "Doe" ,email: "jane&#64;gmail.com" ,job: "architect"}

现在&#xff0c;让我们编写测试&#xff0c;以发送创建新用户的请求。 服务器应处理请求&#xff0c;生成密码&#xff0c;创建新用户&#xff0c;使用生成的密码发送电子邮件并将用户作为json返回。 听起来很多&#xff0c;但我们会慢慢进行。 请注意&#xff0c;您应该尝试涵盖所有“路径”和边缘情况。 首先让我们先测试有效数据&#xff0c;然后再测试无效数据。

describe"tries to create and render" dotest "user with valid data" , %{ conn: conn} doresponse &#61;post(conn, user_path(conn, :create ), user: &#64;valid_data )|> json_response( 201 )assert Repo.get_by(User, name: "Jim" )assert_delivered_email Email.create_mail(response[ "password" ], response[ "email" ])endtest "user with invalid data" , %{ conn: conn} doresponse &#61;post(conn, user_path(conn, :create ), user: %{})|> json_response( 422 )assert response[ "errors" ] !&#61; %{}end
end

每个测试将发布请求发送到特定路径&#xff0c;然后我们检查json响应和断言值。

运行此测试

mixtest test /company_api_web/controller/user_controller_test.exs

会导致错误。 我们没有user_path / 3函数&#xff0c;这意味着未定义路由。 打开lib / company_api_web / router.ex 。 我们将添加范围“ / api”&#xff0c;它将通过&#xff1a;api管道。 我们可以将路由定义为资源&#xff0c;单独或嵌套路由。 定义这样的新资源&#xff1a;

resources "/users", UserController,only : [: index , : create ]

这样&#xff0c;Phoenix将创建路由&#xff0c;这些路由映射到索引并创建函数并由UserController处理。 如果打开控制台并键入

mix phx .routes

您可以看到路线列表&#xff0c;其中有user_path路线&#xff0c;一条路线带动词GET&#xff0c;另一条带动词POST。 现在&#xff0c;如果我们再次运行测试&#xff0c;这一次我们将得到另一个错误&#xff0c;缺少创建函数。 原因是我们没有UserController。 在lib / company_api_web / controllers中添加user_controller.ex。

现在定义新模块&#xff1a;

defmodule CompanyApiWeb.UserController douse CompanyApiWeb, :controller
end

接下来&#xff0c;我们需要创建那个create / 2函数。 Create函数必须接受conn struct&#xff08;并返回它&#xff09;和params。 参数是结构&#xff0c;它承载浏览器提供的所有数据。 我们可以使用Elixir的一项强大功能&#xff0c;即模式匹配&#xff0c;将我们所需的数据与变量进行匹配。

def create (conn, %{ "user" &#61;> user_data}) doparams &#61; Map.put(user_data, "password" , User.generate_password())case Repo.insert(User.reg_changeset(%User{}, params)) do{ :ok , user} ->conn|> put_status( :created )|> render( "create.json" , user: user){ :error , user} ->conn|> put_status( :unprocessable_entity )|> render( "error.json" , user: user)end
end

在我们的测试中&#xff0c;我们通过post方法的params 用户user &#64;valid_data发送该数据将与user_data匹配。 在用户模型中&#xff0c;定义generate_password函数&#xff0c;因此我们可以为每个新用户生成随机密码。

def generate_password do:crypto .strong_rand_bytes( &#64;pass_length )|> Base.encode64|> binary_part( 0 , &#64;pass_length )end

根据需要设置密码的长度。 由于user_data是一个映射&#xff0c;我们将使用键“ password”将新生成的密码放入该映射内。

尽管Elixir具有try / rescue块&#xff0c;但很少使用它们。 通常&#xff0c;大小写和模式匹配的组合用于错误处理。 函数insert&#xff08;注意&#xff0c;我们不会使用insert&#xff01;函数&#xff0c;因为它引发异常&#xff09;返回两个元组之一&#xff1a;

{:ok , Ecto.Schema.t}
{ :error , Ecto.Changeset.t}

基于返回的元组&#xff0c;我们发送适当的响应。 由于我们正在制作JSON API&#xff0c;因此我们应该以json格式返回数据。 从控制器返回的所有数据均由适当的视图处理。 如果我们再次运行测试&#xff0c;将会得到另一个错误。 我们需要做的最后一件事是添加视图文件。 在lib / company_api_web / views /中创建user_view.ex文件&#xff0c;并在其中定义新模块和呈现方法。

defmodule CompanyApiWeb.UserView douse CompanyApiWeb, :viewdef render ( "create.json" , %{ user: user}) dorender_one(user, CompanyApiWeb.UserView, "user.json" )enddef render ( "error.json" , %{ user: user}) do%{ errors: translate_errors(user)}enddef render ( "user.json" , %{ user: user}) do%{ id: user.id, name: user.name, subname: user.subname, password: user.password, email: user.email, job: user.job}enddefp translate_errors (user) doEcto.Changeset.traverse_errors(user, &translate_error/ 1 )end
end

首先从控制器调用render方法&#xff0c;在该方法中我们将调用键&#xff0c;视图模块和模板名称传递给render_one / 3 &#xff0c;因此我们可以对match方法进行模式化。 现在&#xff0c;我们返回将要编码为json的数据。 我们不必调用render_one / 3方法&#xff0c;我们可以立即返回json&#xff0c;但这更加方便。

第二个render方法以json的形式呈现changeset结构提供的错误。 内置方法Ecto.Changeset.traverse_errors / 2changeet.errors结构中提取错误字符串。

如果我们删除断言表明已发送电子邮件的那一行&#xff0c;则测试将通过。 这样就完善了我们测试和编写控制器的方式。 现在&#xff0c;您可以测试和编写索引方法&#xff0c;并添加涵盖更多代码的更多测试用例。

电子邮件发送示例

Elixir中有几个电子邮件库&#xff0c;但是在这个项目中&#xff0c;我们决定使用Bamboo 。 初始设置后&#xff0c;其用法相当简单。 打开mix.exs文件&#xff0c;并在deps函数下添加以下行&#xff1a;

{:bamboo , "~> 0.8" }

然后运行以下命令&#xff1a;

mix deps.get

这将下载依赖项。 之后&#xff0c;在应用程序功能中将Bamboo添加为extra_application。

在全局配置文件中&#xff0c;添加Bamboo的配置&#xff1a;

config:company_api , CompanyApi.Mailer,adapter: Bamboo.LocalAdapter

在这里&#xff0c;我们使用Bamboo.LocalAdapter&#xff0c;但也有其他适配器。 现在&#xff0c;创建模块CompanyApi.Mailer和以下行&#xff1a;

use Bamboo.Mailer, otp_app: :company_api

在使用mailer之前&#xff0c;我们应该定义电子邮件结构。 添加到模型目录中的Email.ex文件&#xff08;请注意&#xff0c;您应该先编写测试文件&#xff0c;然后添加文件&#xff0c;但我们现在将跳过该文件&#xff09;。

defmodule CompanyApiWeb.Email doimport Bamboo.Emaildef create_mail (password, email) donew_email()|> to(email)|> from( "company&#64;gmail.com" )|> subject( "Generated password" )|> html_body( "

Welcome to Chat

" )|> text_body( "Welcome. This is your generated password #{password} . You can change it anytime." )end
end

函数create_mail / 2返回我们将用于发送的电子邮件结构。 在运行测试之前&#xff0c;我们需要在/config/test.exs中添加配置&#xff0c;与以前相同&#xff0c;唯一的区别是适配器&#xff0c;即现在的Bamboo.TestAdapter。 添加这个&#96;use Bamboo.Test&#96;可以在我们的测试中使用诸如&#96;assert_delivered_email&#96;的功能。 现在&#xff0c;在UserController中成功插入后&#xff0c;添加下一行&#xff1a;

Email.create_mail(user.password, user.email)
|> CompanyApi.Mailer.deliver_later

这将创建电子邮件结构并在后台发送它。 对于异步发送&#xff0c;有任务模块。 如果您希望查看已发送的邮件&#xff0c;请在router.exs中添加以下内容&#xff1a;

if Mix.env &#61;&#61;:dev doforward "/send_mails" , Bamboo.EmailPreviewPlug
end

现在&#xff0c;我们可以在localhost&#xff1a;4000 / sent_mails看到传递的邮件。

通过监护人认证

到目前为止&#xff0c;我们已经展示了如何编写测试&#xff0c;迁移&#xff0c;模型&#xff0c;控制器&#xff0c;视图和路由。 更重要的一件事是验证用户身份。 这里选择的图书馆是Guardian 。 它使用JWT&#xff08;Json Web令牌&#xff09;作为身份验证方法&#xff0c;我们可以对Phoenix服务以及通道进行身份验证。 好东西。

首先在mix.exs文件中添加依赖项&#96;{:guardian, "~> 1.0-beta"}&#96;并运行

mix deps.get

在Guardian文档中&#xff0c;有详细的说明如何设置基本配置&#xff0c;但我们将在此处逐步进行。 打开/config/config.exs并添加以下内容&#xff1a;

config:company_api , CompanyApi.Guardian,issuer: "CompanyApi" ,secret_key: "QDG1lCBdCdjwF49UniOpbxgUINhdyvQDcFQUQam&#43;65O4f9DgWRe09BYMEEDU1i9X" ,verify_issuer: true

请注意&#xff0c;CompanyApi.Guardian将成为我们要创建的模块。 您不必称其为Guardian&#xff0c;也许有点多余。 无论如何&#xff0c;接下来的事情是必须生成的secret_key。 这是一个秘密密钥的示例&#xff0c;可以通过运行来生成

mix guardian.gen .secret

lib / company_api /中创建CompanyApi.Guardian模块。

defmodule CompanyApi.Guardian douse Guardian, otp_app: :company_apialias CompanyApi.Repoalias CompanyApiWeb.Userdef subject_for_token (user &#61; %User{}, _claims) do{ :ok , "User: #{user.id} " }enddef subject_for_token ( _ ) do{ :error , "Unknown type" }enddef resource_from_claims (claims) doid &#61; Enum.at(String.split(claims[ "sub" ], ":" ), 1 )case Repo.get(User, String.to_integer(id)) donil ->{ :error , "Unknown type" }user ->{ :ok , user}endend
end

创建令牌时将使用此模块。 我们将用户ID作为令牌的主题&#xff0c;这样我们就可以始终从数据库中获取用户。 这可能是最方便的方法&#xff0c;但不是唯一的方法。 我们要做的下一步是建立守护程序管道。 通过插头使用Guardian很容易。 打开lib / company_api_web / router.ex并添加新管道&#xff1a;

pipeline:auth doplug Guardian.Plug.Pipeline, module: CompanyApi.Guardian,error_handler: CompanyApi.GuardianErrorHandlerplug Guardian.Plug.VerifyHeader, realm: "Bearer"plug Guardian.Plug.EnsureAuthenticatedplug Guardian.Plug.LoadResource, ensure: trueend

该管道可以直接在router.ex文件中定义&#xff0c;也可以在单独的模块中定义&#xff0c;但是仍然需要在此处引用。 当用户尝试调用某些服务时&#xff0c;他的请求将通过管道传递。 请注意&#xff0c;此管道专门用于JSON API

Okey&#xff0c;首先&#xff0c;我们定义我们正在使用插件管道和引用实现模块以及将要处理auth错误的模块&#xff08;我们将创建它&#xff09;。 下一个插件验证令牌是否在请求标头中&#xff0c;插件确保通过AuthenticAuthenticated确保提供了有效的JWT令牌&#xff0c;最后一个插件通过调用CompanyApi.Guardian模块中指定的函数resource_from_claims / 1来加载资源。

由于缺少auth_error处理模块&#xff0c;请将其添加到lib / company_api /中

defmodule CompanyApi.GuardianErrorHandler dodef auth_error (conn, {_type, reason}, _opts) doconn|> Plug.Conn.put_resp_content_type( "application/json" )|> Plug.Conn.send_resp( 401 , Poison.encode!(%{ message: to_string(reason)}))end
end

毒药是Elixir JSON库。 只需在mix.exs中添加依赖项&#96;{:poison, "~> 3.1"}&#96;

我们已经为Guardian设置了所有内容&#xff0c;现在是时候编写SessionController并处理登录和注销了。 首先&#xff0c;我们必须编写测试。 创建session_controller_test.exs。 我们将测试用户登录并使其通过。 我们已经为UserController编写了测试&#xff0c;因此您也知道如何设置这一测试。

test"login as user" , %{ conn: conn, user: user} douser_credentials &#61; %{ email: user.email, password: user.password}response &#61;post(conn, session_path(conn, :create ), creds: user_credentials)|> json_response( 200 )expected &#61; %{"id" &#61;> user.id,"name" &#61;> user.name,"subname" &#61;> user.subname,"password" &#61;> user.password,"email" &#61;> user.email,"job" &#61;> user.job}assert response[ "data" ][ "user" ] &#61;&#61; expectedrefute response[ "data" ][ "token" ] &#61;&#61; nilrefute response[ "data" ][ "expire" ] &#61;&#61; nilend

我们将尝试使用有效的凭据登录&#xff0c;并期望以响应用户身份获得令牌和过期值。 如果我们运行此测试&#xff0c;它将失败。 我们没有session_path路由。 打开router.ex文件&#xff0c;并在我们的“ / api”范围内添加新路由&#xff1a;

post"/login" , SessionController, :create

我们将此路由置于“ / api”范围内&#xff0c;因为我们的用户在尝试登录时不需要进行身份验证。 如果我们再次运行测试&#xff0c;这次将失败&#xff0c;因为没有创建功能。

现在添加SessionController并编写登录功能。

def create (conn, %{ "creds" &#61;> params}) donew_params &#61; Map.new(params, fn {k, v} -> {String.to_atom(k), v} end )case User.check_registration(new_params) do{ :ok , user} ->new_conn &#61; Guardian.Plug.sign_in(conn, CompanyApi.Guardian, user)token &#61; Guardian.Plug.current_token(new_conn)claims &#61; Guardian.Plug.current_claims(new_conn)expire &#61; Map.get(claims, "exp" )new_conn|> put_resp_header( "authorization" , "Bearer #{token} " )|> put_status( :ok )|> render( "login.json" , user: user, token: token, exp: expire){ :error , reason} ->conn|> put_status( 401 )|> render( "error.json" , message: reason)endend

第一行以键为原子的结果创建新地图。 函数check_registration / 1检查数据库中是否存在具有给定凭据的用户。 如果用户存在&#xff0c;我们将其登录&#xff0c;创建新令牌并终止日期。 之后&#xff0c;我们设置响应头&#xff0c;状态和渲染用户。 为了渲染&#xff0c;我们需要在lib / company_api_web / views /中创建session_view.ex。

defmodule CompanyApiWeb.SessionView douse CompanyApiWeb, :viewdef render ( "login.json" , %{ user: user, token: token, exp: expire}) do%{data: %{user: render_one(user, CompanyApiWeb.UserView, "user.json" ),token: token,expire: expire}}enddef render ( "error.json" , %{ message: reason}) do%{ data: reason}end
end

现在测试应该通过了。 当然&#xff0c;应该添加更多测试&#xff0c;但这取决于您。 注销非常简单&#xff0c;&#96;Guardian.revoke&#xff08;CompanyApi.Guardian&#xff0c;token&#xff09;&#96;从标头中删除令牌&#xff0c;这就是我们需要做的。 使用API​​并没有真正的注销&#xff0c;但这是可行的。 在添加新的登出路径之前&#xff0c;我们需要定义“新范围”。 实际上&#xff0c;这将再次成为相同的“ / api”作用域&#xff0c;但是现在它将通过两个管道进行操作&#xff1a; &#96;pipe_through [:api, :auth]&#96;

我们为什么这样做呢&#xff1f; 每个需要认证的新路由都将位于此新范围内。 另外&#xff0c;如果要注销&#xff0c;则需要首先进行身份验证。 这样&#xff0c;我们就涵盖了与Guardian进行身份验证的过程。 稍后将提到套接字身份验证&#xff0c;它甚至更容易。

关联示例

由于这是一个聊天应用程序&#xff0c;因此必须以某种方式保存消息历史记录。 我们将再添加两个实体&#xff0c;它们代表两个用户之间的对话以及用户的消息。 这将是展示Ecto中关联示例的好机会。

我们要添加的第一个实体是对话实体。 对话将同时属于参与聊天的用户&#xff0c;并且该用户将进行许多对话。 对话中还将有许多消息是第二个实体。 消息将属于用户和某些对话。 在这种情况下&#xff0c;用户代表发送消息的人。 消息的其他属性是日期和内容。

我们用几句话描述了我们的数据模型。 这些数据模型中的每一个都有自己的测试&#xff0c;控制器和视图&#xff0c;但是由于我们已经解释了所有这些内容&#xff0c;因此在这一部分中&#xff0c;我们将重点关注这些实体之间的关联。 请注意&#xff0c;您唯一需要做的就是编写用于创建对话&#xff0c;创建消息和获取消息历史记录的功能。

对话内容

首先&#xff0c;让我们添加对话迁移。

运行命令

mix ecto.gen .migration create_conversations

现在&#xff0c;我们需要使用正确的列创建表对话&#xff1a;

def change docreate table( :conversations ) doadd :sender_id , references( :users , null: false )add :recipient_id , references( :users , null: false )timestamps()endcreate unique_index( :conversations , [ :sender_id , :recipient_id ], name: :sender )end

如您所见&#xff0c;我们正在添加外键sender_id和收件人_id&#xff0c;并且正在引用users表。 这将代表我们两个用户的对话。 两个键不能为null。 我们要做的最后一件事是在与唯一约束相对应的两列上创建unique_index。 我们这样做是因为我们不希望重复的对话具有相同的ID。 现在创建模型&#xff1a;

defmodule CompanyApiWeb.Conversation douse CompanyApiWeb, :modelalias CompanyApiWeb.{User, Message}schema "conversations" dofield :status , :stringbelongs_to :sender , User, foreign_key: :sender_idbelongs_to :recipient , User, foreign_key: :recipient_idhas_many :messages , Messagetimestamps()enddef changeset (changeset, params \\ %{}) dochangeset|> cast(params, [ :sender_id , :recipient_id , :status ])|> validate_required([ :sender_id , :recipient_id ])|> unique_constraint( :sender_id , name: :sender )|> foreign_key_constraint( :sender_id )|> foreign_key_constraint( :recipient_id )end

观察新功能。 函数belongs_to / 3has_many / 3表示关联。 通常&#xff0c; belongs_to / 3函数是用名称和引用的模块定义的&#xff0c;但是这一次&#xff0c;因为我们对同一模块有两个引用&#xff0c;所以我们必须添加一个对应的外键列。

has_many / 3关联&#xff0c;关联名称和模块也有同样的情况&#xff08;我们将很快创建Message模块&#xff09;。 现在更改集。 我们添加了两个foreign_key_contraint / 3函数&#xff0c;每个外键一个&#xff0c;并且添加了unique_constraint / 3函数&#xff08;由于复合唯一列&#xff0c;只需要指定一个&#xff09;。 所有这些约束都在数据库级别检查。

讯息

第二实体是消息。 跑

mix ecto.gen .migration create_messages

添加创建和表功能&#xff1a;

def change docreate table( :messages ) doadd :sender_id , references( :users , null: false )add :conversation_id , references( :conversations , null: false )add :content , :varcharadd :date , :naive_datetimetimestamps()endcreate index( :messages , [ :sender_id ])create index( :messages , [ :conversation_id ])end

和以前一样的故事。 消息属于两个外键&#xff0c;消息分别属于用户&#xff08;发送者&#xff09;和会话。 这次我们不需要唯一的约束&#xff0c;所以我们只索引提到的字段。 看一下模型&#xff1a;

defmodule CompanyApiWeb.Message douse CompanyApiWeb, :modelalias CompanyApiWeb.{User, Conversation}schema "messages" dofield :content , :stringfield :date , :naive_datetimebelongs_to :conversation , Conversationbelongs_to :sender , User, foreign_key: :sender_idtimestamps()enddef changeset (changeset, params \\ %{}) dochangeset|> cast(params, [ :sender_id , :conversation_id , :content , :date ])|> validate_required([ :sender_id , :conversation_id , :content , :date ])|> foreign_key_constraint( :sender_id )|> foreign_key_constraint( :conversation_id )end

我们要做的最后一件事是在“用户”模块中添加关联&#xff1a;

has_many:sender_conversations , Conversation, foreign_key: :sender_idhas_many :recipient_conversations , Conversation, foreign_key: :recipient_idhas_many :messages , Message, foreign_key: :sender_id

这样&#xff0c;我们就建立了数据模型&#xff0c;并且您已经看到了Ecto关联的简要示例。 对于many_to_many关联&#xff0c;请阅读docs 。

频道

本质上&#xff0c;通道是基于套接字顶部的Phoenix抽象。 一个套接字连接上可以有多个通道。 有关详细说明和了解渠道推荐的方式&#xff0c;请阅读官方文档 。

我们的目标是通过Websocket协议发送消息&#xff0c;而我们将从编写通道测试开始。 有关频道测试的文档确实很有帮助。

/ test / company_api_web / channels /目录中创建chat_room_test.exs。 在设置块中&#xff0c;将一个用户插入数据库&#xff0c;创建连接并登录用户。 我们将测试消息发送。

defmodule CompanyApiWeb.ChatRoomTest douse CompanyApiWeb.ChannelCasealias CompanyApi.Guardian, as: Guardalias CompanyApiWeb.{ChatRoom, UserSocket, Conversation}&#64;first_user_data %{ name: "John" ,subname: "Doe" ,email: "doe&#64;gmail.com" ,job: "engineer"}&#64;second_user_data %{ name: "Jane" ,subname: "Doe" ,email: "jane&#64;gmail.com" ,job: "architect"}setup douser &#61;%User{}|> User.reg_changeset( &#64;first_user_data )|> Repo.insert!{ :ok , token, _claims} &#61; Guard.encode_and_sign(user){ :ok , soc} &#61; connect(UserSocket, %{ "token" &#61;> token}){ :ok , _ , socket} &#61; subscribe_and_join(soc, ChatRoom, "room:chat" ){ :ok , socket: socket, user: user}endtest "checks messaging" , %{ socket: socket, user: u} douser &#61;%User{}|> User.reg_changeset( &#64;second_user_data )|> Repo.insert!conv &#61;%Conversation{}|> Conversation.changeset(%{ sender_id: u.id, recipient_id: user.id})|> Repo.insert!{ :ok , token, _claims} &#61; Guard.encode_and_sign(user){ :ok , soc} &#61; connect(UserSocket, %{ "token" &#61;> token}){ :ok , _ , socketz} &#61; subscribe_and_join(soc, ChatRoom, "room:chat" )push socket, "send_msg" , %{ user: user.id, conv: conv.id, message: "Hi! This is message" }assert_push "receive_msg" , %{ message: message}assert message.content &#61;&#61; "Hi! This is message"refute Repo.get!(CompanyApiWeb.Message, message.id) &#61;&#61; nilpush socketz, "send_msg" , %{ user: u.id, conv: conv.id, message: "This is a reply" }assert_push "receive_msg" , %{ message: reply}assert reply.content &#61;&#61; "This is a reply"refute Repo.get!(CompanyApiWeb.Message, reply.id) &#61;&#61; nilend
end

好吧&#xff0c;这似乎很多&#xff0c;但请逐步进行。 在设置块中&#xff0c;我们使用生成的令牌连接到套接字&#xff0c;然后函数subscribe_and_join / 3将用户加入列出的主题。 在测试之后&#xff0c;对第二个用户重复这些步骤&#xff0c;然后创建对话。 函数push / 3允许我们直接通过套接字发送消息&#xff0c;而assert_pushassert_broadcast声明推送或广播的消息。 运行测试将导致错误。

打开lib / company_api_web / channels / user_socket.ex并定义新频道

channel "room:*" , CompanyApiWeb.ChatRoom

在这里修改connect / 2id / 1函数。 我们希望使只有经过身份验证的用户才能连接到套接字。

def connect (%{ "token" &#61;> token}, socket) docase Guardian.Phoenix.Socket.authenticate(socket, CompanyApi.Guardian, token) do{ :ok , socket} ->{ :ok , socket}{ :error , _ } ->:errorendenddef connect (_params, _socket), do: :errordef id (socket) douser &#61; Guardian.Phoenix.Socket.current_resource(socket)"user_socket: #{user.id} "end

&#96;Guardian.Phoenix.Socket.authenticate(socket, CompanyApi.Guardian, token)&#96;提供了身份验证。 函数id / 1返回套接字ID&#xff0c;我们将其设置为用户ID。

现在让我们创建一个新频道。 在同一目录中创建channel_room.ex文件&#xff0c;但现在保留它。 由于我们正在进行私人聊天&#xff0c;因此我们需要知道要向其发送消息的套接字。 有一些方法可以实现这一目标。 这里的决定是将打开的套接字连接存储在映射&#96;{user_id: socket}&#96;

Elixir提供了两种用于存储状态的抽象&#xff1a; GenServers和Agent 。 为了理解GenServer或代理文档的概念&#xff0c;必须阅读。

打开lib / company_api /并创建channel_sessions.ex&#xff0c;这将是我们用于存储套接字的GenServer。

defmodule CompanyApi.ChannelSessions douse GenServer#Client sidedef start_link (init_state) doGenServer.start_link(__MODULE_ _ , init_state, name: __MODULE_ _ )enddef save_socket (user_id, socket) doGenServer.call(__MODULE_ _ , { :save_socket , user_id, socket})enddef delete_socket (user_id) doGenServer.call(__MODULE_ _ , { :delete_socket , user_id})enddef get_socket (user_id) doGenServer.call(__MODULE_ _ , { :get_socket , user_id})enddef clear () doGenServer.call(__MODULE_ _ , :clear )end#Server callbacksdef handle_call ({ :save_socket , user_id, socket}, _from, socket_map) docase Map.has_key?(socket_map, user_id) dotrue ->{ :reply , socket_map, socket_map}false ->new_state &#61; Map.put(socket_map, user_id, socket){ :reply , new_state, new_state}endenddef handle_call ({ :delete_socket , user_id}, _from, socket_map) donew_state &#61; Map.delete(socket_map, user_id){ :reply , new_state, new_state}enddef handle_call ({ :get_socket , user_id}, _from, socket_map) dosocket &#61; Map.get(socket_map, user_id){ :reply , socket, socket_map}enddef handle_call ( :clear , _from, state) do{ :reply , %{}, %{}}end
end

GenServer抽象了常见的客户端-服务器交互。 客户端调用服务器端回调。 这些回调对地图进行操作。 该模块应在应用程序启动时启动&#xff0c;因此我们将其添加到Supervision tree中 。 这是Elixir中最美丽的东西之一。

在同一目录中打开application.ex文件&#xff0c;并在子级列表中添加&#96;worker(CompanyApi.ChannelSessions, [%{}])&#96;这一行。 这将以初始状态&#96;%{}&#96;在应用程序的开头启动ChannelSessions。 现在我们可以编写ChatRoom频道。 每个通道必须实现两个回调join / 3handle_in / 3

defmodule CompanyApiWeb.ChatRoom douse CompanyApiWeb, :channelalias CompanyApi.{ChannelSessions, ChannelUsers}alias CompanyApiWeb.Messagedef join ( "room:chat" , _payload, socket) douser &#61; Guardian.Phoenix.Socket.current_resource(socket)send( self (), { :after_join , user}){ :ok , socket}enddef handle_in ( "send_msg" , %{ "user" &#61;> id, "conv" &#61;> conv_id, "message" &#61;> content}, socket) docase ChannelSessions.get_socket id donil ->{ :error , socket}socketz ->user &#61; Guardian.Phoenix.Socket.current_resource(socket)case Message.create_message(user.id, conv_id, content) donil ->{ :noreply , socket}message ->push socketz, "receive_msg" , %{ message: message}{ :noreply , socket}endendenddef handle_info ({ :after_join , user}, socket) doChannelSessions.save_socket(user.id, socket){ :noreply , socket}enddef terminate (_msg, socket) douser &#61; Guardian.Phoenix.Socket.current_resource(socket)ChannelSessions.delete_socket user.idend
end

由于我们需要保存套接字&#xff0c;因此只能在创建套接字后才能将其完成&#xff0c;该套接字位于join / 3回调的末尾。 因此&#xff0c;我们向自己发送消息&#xff0c;该消息将调用回调方法handle_info / 2 。 在那里&#xff0c;我们将套接字添加到地图中。 回调handle_in / 3创建一条消息并将其发送给适当的用户。 函数teminate / 2从地图上删除套接字。

设置完成后&#xff0c;聊天应用程序API已完成。 本教程涵盖了早期列出的所有部分以及OTP的一些高级内容&#xff0c;例如GenServer。 它旨在在开发一个Elixir应用程序时显示工作流程&#xff0c;并且为了完全理解需要文档阅读。 毕竟&#xff0c;这里有所有信息。

所有Elixir爱好者的推荐场所&#xff0c; Elixir论坛 。

先前发布在https://kolosek.com/elixir-basic-api-guide/

翻译自: https://hackernoon.com/basic-elixir-api-guide-2h48u3y7z

rtems api用户指南



推荐阅读
  • 本文探讨了一个Web工程项目的需求,即允许用户随时添加定时任务,并通过Quartz框架实现这些任务的自动化调度。文章将介绍如何设计任务表以存储任务信息和执行周期,以及如何通过一个定期扫描机制自动识别并加载新任务到调度系统中。 ... [详细]
  • 本文详细介绍了Socket在Linux内核中的实现机制,包括基本的Socket结构、协议操作集以及不同协议下的具体实现。通过这些内容,读者可以更好地理解Socket的工作原理。 ... [详细]
  • 如何使用Maven将依赖插件一并打包进JAR文件
    本文详细介绍了在使用Maven构建项目时,如何将所需的依赖插件一同打包进最终的JAR文件中,以避免手动部署依赖库的麻烦。 ... [详细]
  • 本文探讨了Android系统中联系人数据库的设计,特别是AbstractContactsProvider类的作用与实现。文章提供了对源代码的详细分析,并解释了该类如何支持跨数据库操作及事务处理。源代码可从官方Android网站下载。 ... [详细]
  • 探索CNN的可视化技术
    神经网络的可视化在理论学习与实践应用中扮演着至关重要的角色。本文深入探讨了三种有效的CNN(卷积神经网络)可视化方法,旨在帮助读者更好地理解和优化模型。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • Linux内核中的内存反碎片技术解析
    本文深入探讨了Linux内核中实现的内存反碎片技术,包括其历史发展、关键概念如虚拟可移动区域以及具体的内存碎片整理策略。旨在为开发者提供全面的技术理解。 ... [详细]
  • 本文探讨了如何选择一个合适的序列化版本ID(serialVersionUID),包括使用生成器还是简单的整数,以及在不同情况下应如何处理序列化版本ID。 ... [详细]
  • 在CentOS 7中部署Nginx并配置SSL证书
    本文详细介绍了如何在CentOS 7操作系统上安装Nginx服务器,并配置SSL证书以增强网站的安全性。适合初学者和中级用户参考。 ... [详细]
  • STM32代码编写STM32端不需要写关于连接MQTT服务器的代码,连接的工作交给ESP8266来做,STM32只需要通过串口接收和发送数据,间接的与服务器交互。串口三配置串口一已 ... [详细]
  • 本文详细探讨了在Windows 98环境下安装Apache 1.3.9、JServ、GNUJSP 1.0、JDK 1.2.2及JSDK 2.0后遇到的中文显示问题,并提供了多种有效的解决方案。 ... [详细]
  • 本文探讨了如何使用Scrapy框架构建高效的数据采集系统,以及如何通过异步处理技术提升数据存储的效率。同时,文章还介绍了针对不同网站采用的不同采集策略。 ... [详细]
  • Python网络编程:深入探讨TCP粘包问题及解决方案
    本文详细探讨了TCP协议下的粘包现象及其产生的原因,并提供了通过自定义报头解决粘包问题的具体实现方案。同时,对比了TCP与UDP协议在数据传输上的不同特性。 ... [详细]
  • Gradle 是 Android Studio 中默认的构建工具,了解其基本配置对于开发效率的提升至关重要。本文将详细介绍如何在 Gradle 中定义和使用共享变量,以确保项目的一致性和可维护性。 ... [详细]
  • 本文介绍了使用Python和C语言编写程序来计算一个给定数值的平方根的方法。通过迭代算法,我们能够精确地得到所需的结果。 ... [详细]
author-avatar
圣换少爷
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有