Current section
29 Versions
Jump to
Current section
29 Versions
Compare versions
22
files changed
+566
additions
-165
deletions
| @@ -16,10 +16,8 @@ Warning: 0.4.0 is a rewrite for APIs, don't upgrade if you are using 0.3.0. | |
| 16 16 | ```elixir |
| 17 17 | iex(1)> client = Wechat.Client.new(%{appid: "WECHAT_APPID", secret: "WECHAT_SECRET"}) |
| 18 18 | %Wechat.Client{ |
| 19 | - auth: %{ |
| 20 | - appid: "WECHAT_APPID", |
| 21 | - secret: "WECHAT_SECRET" |
| 22 | - }, |
| 19 | + appid: "WECHAT_APPID", |
| 20 | + secret: "WECHAT_SECRET", |
| 23 21 | endpoint: "https://api.weixin.qq.com/" |
| 24 22 | } |
| 25 23 | iex(2)> Wechat.User.get(client) |
| @@ -32,3 +30,75 @@ iex(2)> Wechat.User.get(client) | |
| 32 30 | }} |
| 33 31 | ``` |
| 34 32 | |
| 33 | + ## Plugs |
| 34 | + |
| 35 | + ### Parse wechat message (in Phonenix controller) |
| 36 | + |
| 37 | + * config.exs |
| 38 | + |
| 39 | + ```elixir |
| 40 | + config :my_app, MyApp.Wechat, |
| 41 | + appid: "APP_ID", |
| 42 | + secret: "APP_SECRET", |
| 43 | + token: "TOKEN", |
| 44 | + encoding_aes_key: "ENCODING_AES_KEY" # Required if you enabled the encrypt mode |
| 45 | + ``` |
| 46 | + |
| 47 | + * my_app/wechat.ex |
| 48 | + |
| 49 | + ```elixir |
| 50 | + defmodule MyApp.Wechat do |
| 51 | + use Wechat, otp_app: :beaver |
| 52 | + |
| 53 | + def users do |
| 54 | + client() |> Wechat.User.get() |
| 55 | + end |
| 56 | + end |
| 57 | + ``` |
| 58 | + |
| 59 | + * router.ex |
| 60 | + |
| 61 | + ```elixir |
| 62 | + defmodule MyApp.Router do |
| 63 | + scope "/wechat", MyApp do |
| 64 | + resources "/", WechatController, [:index, :create] |
| 65 | + end |
| 66 | + end |
| 67 | + ``` |
| 68 | + |
| 69 | + * wechat_controller.ex |
| 70 | + |
| 71 | + ```elixir |
| 72 | + defmodule MyApp.WechatController do |
| 73 | + use MyApp.Web, :controller |
| 74 | + |
| 75 | + plug Wechat.Plugs.RequestValidator, module: MyApp.Wechat |
| 76 | + plug Wechat.Plugs.MessageParser, [module: MyApp.Wechat] when action in [:create] |
| 77 | + |
| 78 | + def index(conn, %{"echostr" => echostr}) do |
| 79 | + text conn, echostr |
| 80 | + end |
| 81 | + |
| 82 | + def create(conn, _params) do |
| 83 | + msg = conn.body_params |
| 84 | + reply = echo_reply(msg, msg["Content"]) |
| 85 | + render conn, "text.xml", reply: reply |
| 86 | + end |
| 87 | + |
| 88 | + defp echo_reply(%{"ToUserName" => to, "FromUserName" => from}, content) do |
| 89 | + %{from: to, to: from, content: content} |
| 90 | + end |
| 91 | + end |
| 92 | + ``` |
| 93 | + |
| 94 | + * text.xml.eex |
| 95 | + |
| 96 | + ```xml |
| 97 | + <xml> |
| 98 | + <MsgType><![CDATA[text]]></MsgType> |
| 99 | + <Content><![CDATA[<%= @reply.content %>]]></Content> |
| 100 | + <ToUserName><![CDATA[<%= @reply.to %>]]></ToUserName> |
| 101 | + <FromUserName><![CDATA[<%= @reply.from %>]]></FromUserName> |
| 102 | + <CreateTime><%= DateTime.to_unix(DateTime.utc_now) %></CreateTime> |
| 103 | + </xml> |
| 104 | + ``` |
| @@ -5,15 +5,19 @@ | |
| 5 5 | {<<"files">>, |
| 6 6 | [<<"lib">>,<<"lib/wechat">>,<<"lib/wechat/adapter.ex">>, |
| 7 7 | <<"lib/wechat/util.ex">>,<<"lib/wechat/client.ex">>, |
| 8 | - <<"lib/wechat/message.ex">>,<<"lib/wechat/.DS_Store">>, |
| 8 | + <<"lib/wechat/message.ex">>,<<"lib/wechat/request.ex">>, |
| 9 9 | <<"lib/wechat/qrcode.ex">>,<<"lib/wechat/ticket.ex">>, |
| 10 10 | <<"lib/wechat/media.ex">>,<<"lib/wechat/access_token.ex">>, |
| 11 11 | <<"lib/wechat/user.ex">>,<<"lib/wechat/config.ex">>, |
| 12 | - <<"lib/wechat/error.ex">>,<<"lib/wechat/wxa.ex">>,<<"lib/wechat/adapter">>, |
| 12 | + <<"lib/wechat/error.ex">>,<<"lib/wechat/wxa.ex">>,<<"lib/wechat/utils">>, |
| 13 | + <<"lib/wechat/utils/xml_parser.ex">>, |
| 14 | + <<"lib/wechat/utils/signature_verifier.ex">>, |
| 15 | + <<"lib/wechat/utils/message_encryptor.ex">>,<<"lib/wechat/adapter">>, |
| 13 16 | <<"lib/wechat/adapter/sandbox.ex">>,<<"lib/wechat/adapter/redis.ex">>, |
| 14 | - <<"lib/wechat/tags">>,<<"lib/wechat/tags/.DS_Store">>, |
| 15 | - <<"lib/wechat/plugs">>,<<"lib/wechat/base.ex">>,<<"lib/wechat/menu.ex">>, |
| 16 | - <<"lib/wechat/application.ex">>,<<"lib/wechat.ex">>,<<"lib/wechat_bypass">>, |
| 17 | + <<"lib/wechat/plugs">>,<<"lib/wechat/plugs/request_validator.ex">>, |
| 18 | + <<"lib/wechat/plugs/message_parser.ex">>,<<"lib/wechat/base.ex">>, |
| 19 | + <<"lib/wechat/menu.ex">>,<<"lib/wechat/application.ex">>, |
| 20 | + <<"lib/wechat.ex">>,<<"lib/wechat_bypass">>, |
| 17 21 | <<"lib/wechat_bypass/assertion.ex">>,<<"lib/wechat_bypass/case.ex">>, |
| 18 22 | <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE.txt">>]}. |
| 19 23 | {<<"licenses">>,[<<"MIT">>]}. |
| @@ -24,15 +28,20 @@ | |
| 24 28 | {<<"name">>,<<"httpoison">>}, |
| 25 29 | {<<"optional">>,false}, |
| 26 30 | {<<"repository">>,<<"hexpm">>}, |
| 27 | - {<<"requirement">>,<<"~> 0.13">>}], |
| 31 | + {<<"requirement">>,<<"~> 1.0">>}], |
| 28 32 | [{<<"app">>,<<"jason">>}, |
| 29 33 | {<<"name">>,<<"jason">>}, |
| 30 34 | {<<"optional">>,false}, |
| 31 35 | {<<"repository">>,<<"hexpm">>}, |
| 32 36 | {<<"requirement">>,<<"~> 1.0">>}], |
| 37 | + [{<<"app">>,<<"plug">>}, |
| 38 | + {<<"name">>,<<"plug">>}, |
| 39 | + {<<"optional">>,false}, |
| 40 | + {<<"repository">>,<<"hexpm">>}, |
| 41 | + {<<"requirement">>,<<"~> 1.8">>}], |
| 33 42 | [{<<"app">>,<<"redix">>}, |
| 34 43 | {<<"name">>,<<"redix">>}, |
| 35 44 | {<<"optional">>,true}, |
| 36 45 | {<<"repository">>,<<"hexpm">>}, |
| 37 46 | {<<"requirement">>,<<">= 0.0.0">>}]]}. |
| 38 | - {<<"version">>,<<"0.4.0">>}. |
| 47 | + {<<"version">>,<<"0.4.1">>}. |
| @@ -1,111 +1,49 @@ | |
| 1 1 | defmodule Wechat do |
| 2 | - @moduledoc false |
| 2 | + @moduledoc """ |
| 3 | + Use this module to build your own API wrapper for a client. |
| 3 4 | |
| 4 | - alias Wechat.{Client, Error} |
| 5 | + ## Config |
| 5 6 | |
| 6 | - @access_token_errcodes [40001, 40014, 41001, 42001] |
| 7 | + config :my_app, MyApp.Wechat, |
| 8 | + appid: "APP_ID", |
| 9 | + secret: "APP_SECRET", |
| 10 | + token: "TOKEN", |
| 11 | + encoding_aes_key: "ENCODING_AES_KEY" # Required if you enabled the encrypt mode |
| 7 12 | |
| 8 | - def get(client, path, opts \\ []) do |
| 9 | - do_request(client, :get, path, "", opts) |
| 10 | - end |
| 13 | + ## Example |
| 11 14 | |
| 12 | - def raw_get(client, path, opts \\ []) do |
| 13 | - raw_request(client, :get, path, "", opts) |
| 14 | - end |
| 15 | + def MyApp.Wechat do |
| 16 | + use Wechat, otp_app: :my_app |
| 15 17 | |
| 16 | - def post(client, path, body, opts \\ []) |
| 18 | + def users do |
| 19 | + client() |> Wechat.User.get() |
| 20 | + end |
| 21 | + end |
| 22 | + """ |
| 17 23 | |
| 18 | - def post(client, path, body, opts) when is_map(body) do |
| 19 | - body = Jason.encode!(body) |
| 20 | - do_request(client, :post, path, body, opts) |
| 21 | - end |
| 24 | + alias Wechat.Client |
| 22 25 | |
| 23 | - def post(client, path, body, opts), |
| 24 | - do: do_request(client, :post, path, body, opts) |
| 26 | + @callback client() :: Client.t() |
| 25 27 | |
| 26 | - defp raw_request(client, method, path, body, opts) do |
| 27 | - url = client.endpoint <> path |
| 28 | + @callback config() :: Keyword.t() |
| 28 29 | |
| 29 | - HTTPoison.request!(method, url, body, [], opts) |
| 30 | - |> process_response() |
| 31 | - end |
| 30 | + defmacro __using__(opts) do |
| 31 | + quote bind_quoted: [opts: opts] do |
| 32 | + @behaviour Wechat |
| 32 33 | |
| 33 | - defp do_request(client, method, path, body, opts, retries \\ 3) do |
| 34 | - url = client.endpoint <> path |
| 35 | - opts = with_auth(opts, client.auth) |
| 34 | + otp_app = Keyword.fetch!(opts, :otp_app) |
| 36 35 | |
| 37 | - retry? = |
| 38 | - case client.auth do |
| 39 | - %{appid: _} -> retries > 0 |
| 40 | - _ -> false |
| 36 | + @otp_app otp_app |
| 37 | + |
| 38 | + @impl true |
| 39 | + def config do |
| 40 | + Application.get_env(@otp_app, __MODULE__, []) |
| 41 41 | end |
| 42 42 | |
| 43 | - result = |
| 44 | - HTTPoison.request!(method, url, body, [], opts) |
| 45 | - |> process_response() |
| 46 | - |
| 47 | - case result do |
| 48 | - {:ok, body} -> |
| 49 | - {:ok, body} |
| 50 | - |
| 51 | - {:error, %Error{code: code}} when code in @access_token_errcodes and retry? -> |
| 52 | - client |
| 53 | - |> Client.get_token!() |
| 54 | - |> do_request(method, path, body, opts, retries - 1) |
| 55 | - |
| 56 | - {:error, error} -> |
| 57 | - {:error, error} |
| 58 | - end |
| 59 | - end |
| 60 | - |
| 61 | - defp with_auth(opts, %{access_token: access_token}), |
| 62 | - do: add_param_to_opts(opts, :access_token, access_token) |
| 63 | - |
| 64 | - defp with_auth(opts, %{appid: _} = auth) do |
| 65 | - access_token = Client.access_token(auth) |
| 66 | - add_param_to_opts(opts, :access_token, access_token) |
| 67 | - end |
| 68 | - |
| 69 | - defp add_param_to_opts(opts, name, value) do |
| 70 | - if Keyword.has_key?(opts, :params) do |
| 71 | - update_in(opts[:params], &Keyword.put(&1, name, value)) |
| 72 | - else |
| 73 | - Keyword.put(opts, :params, [{name, value}]) |
| 74 | - end |
| 75 | - end |
| 76 | - |
| 77 | - defp process_response(%{status_code: 200, body: body, headers: headers} = response) do |
| 78 | - case handle_body(body, headers) do |
| 79 | - %{"errcode" => errcode, "errmsg" => errmsg} when errcode > 0 -> |
| 80 | - {:error, %Error{type: :api, code: errcode, reason: errmsg, response: response}} |
| 81 | - |
| 82 | - body -> |
| 83 | - {:ok, body} |
| 84 | - end |
| 85 | - end |
| 86 | - |
| 87 | - defp process_response(%{status_code: status_code} = response), |
| 88 | - do: raise(%Error{type: :http, code: status_code, response: response}) |
| 89 | - |
| 90 | - defp handle_body(body, headers) when is_list(headers) do |
| 91 | - content_type = content_type(headers) |
| 92 | - handle_body(body, content_type) |
| 93 | - end |
| 94 | - |
| 95 | - defp handle_body(body, "application/json"), do: Jason.decode!(body) |
| 96 | - defp handle_body(body, "text/plain"), do: Jason.decode!(body) |
| 97 | - defp handle_body(body, _), do: body |
| 98 | - |
| 99 | - defp content_type([]), do: "" |
| 100 | - |
| 101 | - defp content_type([{key, value} | t]) do |
| 102 | - cond do |
| 103 | - String.match?(key, ~r/content-type/i) -> |
| 104 | - [content_type | _] = String.split(value, ";") |
| 105 | - content_type |
| 106 | - |
| 107 | - true -> |
| 108 | - content_type(t) |
| 43 | + @impl true |
| 44 | + def client do |
| 45 | + Wechat.Client.new(config()) |
| 46 | + end |
| 109 47 | end |
| 110 48 | end |
| 111 49 | end |
| @@ -1,15 +1,15 @@ | |
| 1 1 | defmodule Wechat.Base do |
| 2 2 | @moduledoc false |
| 3 3 | |
| 4 | - import Wechat |
| 4 | + alias Wechat.Request |
| 5 5 | |
| 6 6 | def token(client) do |
| 7 7 | params = [ |
| 8 | - appid: client.auth.appid, |
| 9 | - secret: client.auth.secret, |
| 8 | + appid: client.appid, |
| 9 | + secret: client.secret, |
| 10 10 | grant_type: :client_credential |
| 11 11 | ] |
| 12 12 | |
| 13 | - raw_get(client, "cgi-bin/token", params: params) |
| 13 | + Request.raw_get(client, "cgi-bin/token", params: params) |
| 14 14 | end |
| 15 15 | end |
Loading more files…