sdlan/src/sdlan_api.erl
2026-04-02 14:26:48 +08:00

191 lines
6.9 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]).
-spec get_all_networks() -> {ok, [NetworkId :: integer()]} | {error, Reason :: any()}.
get_all_networks() ->
case catch do_get("get_all_networks", []) of
{ok, Resp} ->
case catch jiffy:decode(Resp, [return_maps]) of
#{<<"result">> := Networks} ->
{ok, Networks};
#{<<"error">> := #{<<"code">> := Code, <<"message">> := Message}} ->
{error, {Code, Message}};
_ ->
{error, <<"invalid json">>}
end;
Error ->
Error
end.
-spec get_network(Id :: integer()) -> {ok, Network :: map()} | {error, Reason :: any()}.
get_network(Id) when is_integer(Id) ->
case catch do_get("get_network", [{<<"id">>, integer_to_binary(Id)}]) of
{ok, Resp} ->
case catch jiffy:decode(Resp, [return_maps]) of
#{<<"result">> := Network} ->
{ok, Network};
#{<<"error">> := #{<<"code">> := Code, <<"message">> := Message}} ->
{error, {Code, Message}};
_ ->
{error, <<"invalid json">>}
end;
Error ->
Error
end.
-spec auth_access_token(Params :: map()) -> {ok, Resp :: map()} | {error, Reason :: any()}.
auth_access_token(Params) when is_map(Params) ->
case catch do_post("auth/access_token", Params) of
{ok, Resp} ->
case catch jiffy:decode(Resp, [return_maps]) of
Result when is_map(Result) ->
{ok, Result};
{error, Reason} ->
{error, Reason}
end;
Error ->
Error
end.
-spec set_node_status(Params :: map()) -> {ok, Resp :: map()} | {error, Reason :: any()}.
set_node_status(Params) when is_map(Params) ->
case catch do_post("set_node_status", Params) of
{ok, Resp} ->
{ok, catch jiffy:decode(Resp, [return_maps])};
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 catch do_post("client_flow_report", Params) of
{ok, Resp} ->
{ok, catch jiffy:decode(Resp, [return_maps])};
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 catch do_post("network_forward_report", Params) of
{ok, Resp} ->
{ok, catch jiffy:decode(Resp, [return_maps])};
Error ->
Error
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(jiffy:encode(Params, [force_utf8])),
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).