defmodule WeChat do
@moduledoc """
WeChat SDK for Elixir
## 定义 `Client` 模块
### 公众号(默认)
defmodule YourApp.WeChatAppCodeName do
@moduledoc "CodeName"
use WeChat,
appid: "wx-appid",
appsecret: "appsecret"
end
### 小程序
defmodule YourApp.WeChatAppCodeName do
@moduledoc "CodeName"
use WeChat,
app_type: :mini_program,
appid: "wx-appid",
appsecret: "appsecret"
end
### 第三方应用
defmodule YourApp.WeChatAppCodeName do
@moduledoc "CodeName"
use WeChat,
by_component?: true,
app_type: :official_account | :mini_program, # 默认为 :official_account
appid: "wx-appid",
component_appid: "wx-third-appid", # 第三方 appid
end
### 企业微信
defmodule YourApp.WeChatAppCodeName do
@moduledoc "CodeName"
use WeChat.Work,
corp_id: "corp_id",
agents: [%Work.Agent{name: :agent_name, id: 10000, secret: "your_secret"}, ...]
end
## 参数说明
请看 `t:options/0`
## 接口调用
支持两种方式调用:
- 调用 `client` 方法:
`YourApp.WeChatAppCodeName.Material.batch_get_material(:image, 2)`
- 原生调用方法
`WeChat.Material.batch_get_material(YourApp.WeChatAppCodeName, :image, 2)`
"""
import WeChat.Utils, only: [doc_link_prefix: 0]
alias WeChat.{Work, Storage.Cache}
@typedoc """
OpenID 普通用户的标识,对当前公众号唯一
加密后的微信号,每个用户对每个公众号的 `OpenID` 是唯一的。对于不同公众号,同一用户的 `OpenID` 不同
[Docs Link](#{doc_link_prefix()}/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html){:target="_blank"}
"""
@type openid :: String.t()
@type openid_list :: [openid]
@typedoc """
UnionID 不同应用下的唯一ID
同一用户,对同一个微信开放平台下的不同应用,`UnionID` 是相同的
[Docs Link](#{doc_link_prefix()}/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html){:target="_blank"}
"""
@type unionid :: String.t()
@typedoc """
服务器角色
`:client`: 默认,主动刷新 `AccessToken`
`:hub`: 中控服务器,主动刷新 `AccessToken`
`:hub_client`: 逻辑服务器,从 `hub` 获取 `AccessToken`
"""
@type server_role :: :client | :hub | :hub_client
@typedoc "是否第三方平台开发"
@type by_component? :: boolean
@typedoc """
`client` 的应用类型
- `:official_account`: 公众号
- `:mini_program`: 小程序
"""
@type app_type :: :official_account | :mini_program
@typedoc "公众号/小程序 应用id"
@type appid :: String.t()
@typedoc "公众号/小程序 应用代码"
@type code_name :: String.t()
@typedoc "应用秘钥"
@type appsecret :: String.t()
@typedoc "第三方平台应用id"
@type component_appid :: String.t()
@typedoc "第三方平台应用秘钥"
@type component_appsecret :: String.t()
@typedoc """
服务器配置里的 `token` 值,在接收消息时用于校验签名
"""
@type token :: String.t()
@typedoc "错误码"
@type err_code :: non_neg_integer
@typedoc "错误信息"
@type err_msg :: String.t()
@typep env :: String.t()
@typep url :: String.t()
@typedoc """
参数
## 参数说明
- `appid`: `t:appid/0` - 必填
- `app_type`: `t:app_type/0`
- `code_name`: `t:code_name/0`, 如不指定,默认为模块名最后一个名称的全小写格式
- `by_component?`: `t:by_component?/0`
- `server_role`: `t:server_role/0`
- `storage`: `t:WeChat.Storage.Adapter.t()`
- `appsecret`: `t:appsecret/0` - 仅在 `by_component?` 设定为 `false` 时才有效
- `component_appid`: `t:component_appid/0` - 仅在 `by_component?` 设定为 `true` 时才有效
- `component_appsecret`: `t:component_appsecret/0` - 仅在 `by_component?` 设定为 `true` 时才有效
- `encoding_aes_key`: `t:WeChat.ServerMessage.Encryptor.encoding_aes_key/0` - 在编译时会自动将 `encoding_aes_key` 转换为 `aes_key`
- `token`: `t:token/0`
- `requester`: 请求客户端 - `t:module/0`
- `gen_sub_module?`: 是否生成子模块,默认生成
- `sub_modules`: 指定生成子模块的列表
## 默认参数:
- `server_role`: `:client`
- `by_component?`: `false`
- `app_type`: `:official_account`
- `storage`: `WeChat.Storage.File`
- `requester`: `WeChat.Requester`
- `gen_sub_module?`: true
"""
@type options :: [
server_role: server_role,
by_component?: by_component?,
app_type: app_type,
storage: WeChat.Storage.Adapter.t(),
appid: appid,
appsecret: appsecret,
component_appid: component_appid,
component_appsecret: component_appsecret,
encoding_aes_key: WeChat.ServerMessage.Encryptor.encoding_aes_key(),
token: token,
requester: module
]
@type client :: module()
@type requester :: module()
@type response :: Tesla.Env.result()
@doc false
defmacro __using__(options \\ []) do
quote do
use WeChat.Builder.OfficialAccount, unquote(options)
end
end
@doc """
通过 `appid` 或者 `code_name` 获取 `client`
"""
@spec get_client(appid | code_name) :: nil | client
defdelegate get_client(app_flag), to: WeChat.Storage.Cache, as: :search_client
@spec get_client_agent(appid | code_name, WeChat.Work.agent() | String.t()) ::
nil | {client, Work.Agent.t()}
defdelegate get_client_agent(app_flag, agent_flag),
to: WeChat.Storage.Cache,
as: :search_client_agent
@doc "动态构建 client"
@spec build_client(client, options) :: {:ok, client}
def build_client(client, options) do
with {:module, module, _binary, _term} <-
Module.create(
client,
quote do
@moduledoc false
use WeChat.Builder.OfficialAccount, unquote(Macro.escape(options))
end,
Macro.Env.location(__ENV__)
) do
{:ok, module}
end
end
# hub_url
@doc "set hub_springboard_url for hub client"
@spec set_hub_springboard_url(client, url) :: true
def set_hub_springboard_url(client, url) when is_binary(url) do
Cache.put_cache(client.appid(), :hub_springboard_url, url)
end
@doc "set hub_springboard_url for hub client"
@spec set_hub_springboard_url(client, Work.agent(), url) :: true
def set_hub_springboard_url(client, agent, url) when is_binary(url) do
client.agent2cache_id(agent)
|> Cache.put_cache(:hub_springboard_url, url)
end
@spec get_hub_springboard_url(client) :: nil | url
def get_hub_springboard_url(client) do
Cache.get_cache(client.appid(), :hub_springboard_url)
end
@spec get_hub_springboard_url(client, Work.agent()) :: nil | url
def get_hub_springboard_url(client, agent) do
client.agent2cache_id(agent)
|> Cache.get_cache(:hub_springboard_url)
end
# oauth2_env_url
@doc "set oauth2_env_url for hub server"
@spec set_oauth2_env_url(client, env, url) :: true
def set_oauth2_env_url(client, env, url) when is_binary(env) and is_binary(url) do
Cache.put_cache(client.appid(), {:oauth2_env_url, env}, url)
end
@doc "set oauth2_env_url for hub server"
@spec set_oauth2_env_url(client, Work.agent(), env, url) :: true
def set_oauth2_env_url(client, agent, env, url) when is_binary(env) and is_binary(url) do
client.agent2cache_id(agent)
|> Cache.put_cache({:oauth2_env_url, env}, url)
end
@spec get_oauth2_env_url(client, env) :: nil | url
def get_oauth2_env_url(client, env) do
Cache.get_cache(client.appid(), {:oauth2_env_url, env})
end
@spec get_oauth2_env_url(client, Work.agent(), env) :: nil | url
def get_oauth2_env_url(client, agent, env) do
client.agent2cache_id(agent)
|> Cache.get_cache({:oauth2_env_url, env})
end
def refresher do
Application.get_env(:wechat, :refresher, WeChat.Refresher.Default)
end
def add_to_refresher(client, options \\ %{}) do
m = refresher()
m.add(client, options)
end
end