%%%------------------------------------------------------------------- %%% @author anlicheng %%% @copyright (C) 2024, %%% @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, 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, 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, <<"">>)), <>, 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, <<"">>)), <>, 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).