235 lines
8.3 KiB
Erlang
235 lines
8.3 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @author anlicheng
|
|
%%% @copyright (C) 2024, <COMPANY>
|
|
%%% @doc
|
|
%%%
|
|
%%% @end
|
|
%%% Created : 27. 3月 2024 16:17
|
|
%%%-------------------------------------------------------------------
|
|
-module(sdlan_api).
|
|
-author("anlicheng").
|
|
|
|
-define(API_TOKEN, <<"H6p*2RfEu4ITcL">>).
|
|
|
|
%% API
|
|
-export([get_all_networks/0, get_network/1]).
|
|
-export([flow_report/5, network_forward_report/2, auth_access_token/1, set_node_status/1]).
|
|
-export([node_acl/1]).
|
|
|
|
-spec get_all_networks() -> {ok, [NetworkId :: integer()]} | {error, Reason :: any()}.
|
|
get_all_networks() ->
|
|
case safe_request(fun() -> do_get("get_all_networks", []) end) of
|
|
{ok, Resp} ->
|
|
case decode_json(Resp) of
|
|
{ok, #{<<"result">> := Networks}} ->
|
|
{ok, Networks};
|
|
{ok, #{<<"error">> := #{<<"code">> := _Code, <<"message">> := Message}}} ->
|
|
{error, Message};
|
|
{ok, _} ->
|
|
{error, <<"invalid json">>};
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end;
|
|
{error, _Reason} = Error ->
|
|
Error
|
|
end.
|
|
|
|
-spec get_network(Id :: integer()) -> {ok, Network :: map()} | {error, Reason :: any()}.
|
|
get_network(Id) when is_integer(Id) ->
|
|
case safe_request(fun() -> do_get("get_network", [{<<"id">>, integer_to_binary(Id)}]) end) of
|
|
{ok, Resp} ->
|
|
case decode_json(Resp) of
|
|
{ok, #{<<"result">> := Network}} ->
|
|
{ok, Network};
|
|
{ok, #{<<"error">> := #{<<"code">> := _Code, <<"message">> := Message}}} ->
|
|
{error, Message};
|
|
{ok, _} ->
|
|
{error, <<"invalid json">>};
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end;
|
|
{error, _Reason} = Error ->
|
|
Error
|
|
end.
|
|
|
|
-spec auth_access_token(Params :: map()) -> {ok, Resp :: map()} | {error, Reason :: any()}.
|
|
auth_access_token(Params) when is_map(Params) ->
|
|
case safe_request(fun() -> do_post("auth/access_token", Params) end) of
|
|
{ok, Resp} ->
|
|
case decode_json(Resp) of
|
|
{ok, Result} ->
|
|
{ok, Result};
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end;
|
|
{error, _Reason} = Error ->
|
|
Error
|
|
end.
|
|
|
|
-spec node_acl(Params :: map()) -> {ok, Resp :: map()} | {error, Reason :: any()}.
|
|
node_acl(Params) when is_map(Params) ->
|
|
case safe_request(fun() -> do_post("acl", Params) end) of
|
|
{ok, Resp} ->
|
|
case decode_json(Resp) of
|
|
{ok, Result} ->
|
|
{ok, Result};
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end;
|
|
{error, _Reason} = Error ->
|
|
Error
|
|
end.
|
|
|
|
-spec set_node_status(Params :: map()) -> {ok, Resp :: map()} | {error, Reason :: any()}.
|
|
set_node_status(Params) when is_map(Params) ->
|
|
case safe_request(fun() -> do_post("set_node_status", Params) end) of
|
|
{ok, Resp} ->
|
|
decode_json(Resp);
|
|
{error, _Reason} = Error ->
|
|
Error
|
|
end.
|
|
|
|
-spec flow_report(ClientId :: binary(), NetworkId :: integer(), ForwardNum :: integer(), P2PNum :: integer(), InboundNum :: integer()) ->
|
|
{ok, Resp :: map()} | {error, Reason :: any()}.
|
|
flow_report(ClientId, NetworkId, ForwardNum, P2PNum, InboundNum)
|
|
when is_binary(ClientId), is_integer(NetworkId), is_integer(ForwardNum), is_integer(P2PNum), is_integer(InboundNum) ->
|
|
Params = #{
|
|
<<"client_id">> => ClientId,
|
|
<<"network_id">> => NetworkId,
|
|
<<"forward_num">> => ForwardNum,
|
|
<<"p2p_num">> => P2PNum,
|
|
<<"inbound_num">> => InboundNum
|
|
},
|
|
case safe_request(fun() -> do_post("client_flow_report", Params) end) of
|
|
{ok, Resp} ->
|
|
decode_json(Resp);
|
|
{error, _Reason} = Error ->
|
|
Error
|
|
end.
|
|
|
|
-spec network_forward_report(NetworkId :: integer(), ForwardNum :: integer()) ->
|
|
{ok, Resp :: map()} | {error, Reason :: any()}.
|
|
network_forward_report(NetworkId, ForwardNum) when is_integer(NetworkId), is_integer(ForwardNum) ->
|
|
Params = #{
|
|
<<"network_id">> => NetworkId,
|
|
<<"forward_num">> => ForwardNum
|
|
},
|
|
case safe_request(fun() -> do_post("network_forward_report", Params) end) of
|
|
{ok, Resp} ->
|
|
decode_json(Resp);
|
|
{error, _Reason} = Error ->
|
|
Error
|
|
end.
|
|
|
|
-spec safe_request(Fun :: fun(() -> {ok, binary()} | {error, any()})) -> {ok, binary()} | {error, any()}.
|
|
safe_request(Fun) when is_function(Fun, 0) ->
|
|
case catch Fun() of
|
|
{ok, _Resp} = Ok ->
|
|
Ok;
|
|
{error, _Reason} = Error ->
|
|
Error;
|
|
{'EXIT', Reason} ->
|
|
{error, Reason};
|
|
Error ->
|
|
{error, Error}
|
|
end.
|
|
|
|
-spec decode_json(Resp :: binary()) -> {ok, map()} | {error, any()}.
|
|
decode_json(Resp) when is_binary(Resp) ->
|
|
try json:decode(Resp) of
|
|
Result when is_map(Result) ->
|
|
{ok, Result};
|
|
Other ->
|
|
{error, {invalid_json, Other}}
|
|
catch error:Reason ->
|
|
{error, Reason}
|
|
end.
|
|
|
|
-spec do_get(Uri :: string(), Params :: [{K :: binary(), V :: binary()}]) -> {ok, Response :: binary()} | {error, Reason :: any()}.
|
|
do_get(Uri, Params) when is_list(Uri), is_list(Params) ->
|
|
{ok, Url0} = application:get_env(sdlan, api_url),
|
|
|
|
Headers = [
|
|
{<<"Content-Type">>, <<"application/json">>},
|
|
{<<"X-sign">>, sign_params(Params)}
|
|
],
|
|
|
|
QS0 = uri_string:compose_query(Params),
|
|
QS = iolist_to_binary(QS0),
|
|
|
|
Url = Url0 ++ Uri ++ "?" ++ binary_to_list(QS),
|
|
case catch hackney:request(get, Url, Headers, <<>>, [{pool, false}]) of
|
|
{ok, 200, _, ClientRef} ->
|
|
{ok, RespBody} = hackney:body(ClientRef),
|
|
hackney:close(ClientRef),
|
|
{ok, RespBody};
|
|
{ok, HttpCode, _, ClientRef} ->
|
|
{ok, RespBody} = hackney:body(ClientRef),
|
|
hackney:close(ClientRef),
|
|
{error, {HttpCode, RespBody}};
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end.
|
|
|
|
-spec do_post(Uri :: string(), Params :: map()) -> {ok, Resp :: binary()} | {error, Reason :: any()}.
|
|
do_post(Uri, Params) when is_list(Uri), is_map(Params) ->
|
|
{ok, Url0} = application:get_env(sdlan, api_url),
|
|
|
|
Headers = [
|
|
{<<"content-type">>, <<"application/json">>},
|
|
{<<"X-sign">>, sign_params(Params)}
|
|
],
|
|
|
|
Body = iolist_to_binary(json:encode(Params)),
|
|
Url = Url0 ++ Uri,
|
|
|
|
case catch hackney:request(post, Url, Headers, Body, [{pool, false}]) of
|
|
{ok, 200, _, ClientRef} ->
|
|
{ok, RespBody} = hackney:body(ClientRef),
|
|
hackney:close(ClientRef),
|
|
{ok, RespBody};
|
|
{ok, HttpCode, _, ClientRef} ->
|
|
{ok, RespBody} = hackney:body(ClientRef),
|
|
hackney:close(ClientRef),
|
|
{error, {HttpCode, RespBody}};
|
|
{ok, HttpCode, _} ->
|
|
{error, {HttpCode, <<"empty response">>}};
|
|
{ok, ClientRef} ->
|
|
hackney:close(ClientRef),
|
|
{error, <<"empty response">>};
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end.
|
|
|
|
-spec sign_params(Params :: any()) -> binary().
|
|
sign_params(Params) when is_list(Params) ->
|
|
%% 签名逻辑
|
|
Keys = lists:map(fun({K, _}) -> K end, Params),
|
|
Parts = lists:map(fun(K) ->
|
|
V = as_binary(proplists:get_value(K, Params, <<"">>)),
|
|
<<K/binary, "=", V/binary>>
|
|
end, lists:sort(Keys)),
|
|
SignBin = iolist_to_binary(lists:join(<<"&">>, Parts)),
|
|
logger:debug("sign: ~p, params: ~p", [SignBin, Params]),
|
|
|
|
list_to_binary(sdlan_util:hmac(?API_TOKEN, SignBin));
|
|
sign_params(Params) when is_map(Params) ->
|
|
SortedKeys = lists:sort(maps:keys(Params)),
|
|
Parts = lists:map(fun(K) ->
|
|
V = as_binary(maps:get(K, Params, <<"">>)),
|
|
<<K/binary, "=", V/binary>>
|
|
end, SortedKeys),
|
|
SignBin = iolist_to_binary(lists:join(<<"&">>, Parts)),
|
|
logger:debug("sign: ~p, params: ~p", [SignBin, Params]),
|
|
list_to_binary(sdlan_util:hmac(?API_TOKEN, SignBin)).
|
|
|
|
-spec as_binary(Bin :: any()) -> binary().
|
|
as_binary(Bin) when is_binary(Bin) ->
|
|
Bin;
|
|
as_binary(I) when is_integer(I) ->
|
|
integer_to_binary(I);
|
|
as_binary(F) when is_float(F) ->
|
|
float_to_binary(F, [short]);
|
|
as_binary(Str) when is_list(Str) ->
|
|
list_to_binary(Str).
|