add terminal_handler

This commit is contained in:
安礼成 2023-04-19 17:16:15 +08:00
parent e25469d1c5
commit 2e2b0cab8b
5 changed files with 306 additions and 18 deletions

View File

@ -90,7 +90,7 @@
%% id生成器
-record(id_generator, {
name :: string(),
name :: atom(),
seq = 0 :: integer()
}).

View File

@ -48,7 +48,7 @@ handle_request("POST", "/host/create", _, HostInfo) ->
end;
%%
handle_request("POST", "/host/batch_create", _, HostInfos) ->
handle_request("POST", "/host/batch_import", _, HostInfos) ->
lager:debug("[host_handler] batch_create post params: ~p", [HostInfos]),
%% serial_number是必填项

View File

@ -0,0 +1,206 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 26. 4 2020 3:36
%%%-------------------------------------------------------------------
-module(http_terminal_handler).
-author("licheng5").
-include("iot.hrl").
%% API
-export([handle_request/4]).
%%
handle_request("POST", "/terminal/create", _, TerminalInfo) ->
lager:debug("[terminal_handler] create post params: ~p", [TerminalInfo]),
case valid_terminal_info(TerminalInfo) of
true ->
TerminalId = id_generator_model:new_terminal_id(),
Terminal = convert_terminal_info(TerminalId, TerminalInfo),
case terminal_model:add_terminal(Terminal) of
ok ->
{ok, 200, iot_util:json_data(TerminalId)};
{error, Reason} when is_binary(Reason) ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(-1, Reason)};
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, <<"database error">>)}
end;
false ->
Error = terminal_error(TerminalInfo),
{ok, 200, iot_util:json_error(-1, Error)}
end;
%%
handle_request("POST", "/terminal/batch_import", _, TerminalInfos) ->
lager:debug("[terminal_handler] batch_import post params: ~p", [TerminalInfos]),
%% serial_number是必填项
case lists:any(fun(Info) -> not is_map_key(<<"serial_number">>, Info) orelse maps:get(<<"serial_number">>, Info) == <<"">> end, TerminalInfos) of
true ->
{ok, 200, iot_util:json_error(-1, <<"serial_number missed">>)};
false ->
case lists:all(fun valid_terminal_info/1, TerminalInfos) of
true ->
ImportResult = lists:map(fun(TerminalInfo = #{<<"serial_number">> := SerialNumber}) ->
TerminalId = id_generator_model:new_terminal_id(),
Terminal = convert_terminal_info(TerminalId, TerminalInfo),
case terminal_model:add_terminal(Terminal) of
ok ->
{SerialNumber, TerminalId};
{error, Reason} when is_binary(Reason) ->
lager:debug("[host_handler] add_host get error: ~p", [Reason]),
{SerialNumber, Reason};
{error, Reason} ->
{SerialNumber, <<"failed">>}
end
end, TerminalInfos),
{ok, 200, iot_util:json_data(ImportResult)};
false ->
%%
InvalidTerminalInfos = lists:filter(fun(Info) -> not valid_terminal_info(Info) end, TerminalInfos),
ErrorInfos = lists:map(fun(Info = #{<<"serial_number">> := SerialNumber}) -> {SerialNumber, terminal_error(Info)} end, InvalidTerminalInfos),
{ok, 200, iot_util:json_error(-1, maps:from_list(ErrorInfos))}
end
end;
%%
handle_request("GET", "/terminal/stat", _, _) ->
case terminal_model:get_stat() of
{ok, {TotalNum, Stat}} ->
StatInfo = #{
<<"total_num">> => TotalNum,
<<"stat">> => lists:map(fun({Status, Num}) -> #{<<"status">> => Status, <<"num">> => Num} end, maps:to_list(Stat))
},
{ok, 200, iot_util:json_data(StatInfo)};
{error, Reason} ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, <<"database error">>)}
end;
%%
handle_request(_, "/terminal/list", GetParams, PostParams) ->
Page0 = maps:get(<<"page">>, GetParams, <<"1">>),
Size0 = maps:get(<<"size">>, GetParams, <<"10">>),
Page = binary_to_integer(Page0),
Size = binary_to_integer(Size0),
true = Page > 0 andalso Size > 0,
Start = (Page - 1) * Size,
%%
Model = maps:get(<<"model">>, PostParams, <<"">>),
CellId = maps:get(<<"cell_id">>, PostParams, <<"">>),
CellId1 = case CellId =/= <<>> of
true -> binary_to_integer(CellId);
false -> 0
end,
case terminal_model:find_terminals([{model, Model}, {cell, CellId1}], Start, Size) of
{ok, Terminals, TotalNum} ->
Response = #{
<<"total_num">> => TotalNum,
<<"terminals">> => lists:map(fun terminal_model:to_map/1, Terminals)
},
lager:debug("resp is: ~p", [Response]),
{ok, 200, iot_util:json_data(Response)};
{error, Reason} ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, <<"database error">>)}
end;
%%
handle_request("GET", "/terminal/detail", #{<<"terminal_id">> := TerminalId}, _) ->
lager:debug("[terminal_handler] terminal detail id is: ~p", [TerminalId]),
case terminal_model:get_terminal(TerminalId) of
undefined ->
{ok, 200, iot_util:json_error(404, <<"terminal not found">>)};
{ok, Terminal} ->
TerminalInfo = terminal_model:to_map(Terminal),
%% TODO
{ok, 200, iot_util:json_data(TerminalInfo)}
end;
%%
handle_request("POST", "/terminal/change_status", _, #{<<"terminal_id">> := TerminalId, <<"status">> := Status}) when is_integer(TerminalId), is_integer(Status) ->
lager:debug("[terminal_handler] change_status id is: ~p", [TerminalId]),
case terminal_model:change_status(TerminalId, Status) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, Reason)};
ok ->
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/terminal/update", _, Fields0 = #{<<"terminal_id">> := TerminalId}) ->
lager:debug("[terminal_handler] update terminal params is: ~p", [Fields0]),
Fields = maps:remove(<<"terminal_id">>, Fields0),
case terminal_model:update_terminal(TerminalId, Fields) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, Reason)}
end;
handle_request(_, Path, _, _) ->
Path1 = list_to_binary(Path),
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% map转换成record
convert_terminal_info(TerminalId, TerminalInfo) when is_integer(TerminalId), is_map(TerminalInfo) ->
#terminal{
terminal_id = TerminalId,
%%
host_id = maps:get(<<"host_id">>, TerminalInfo, <<"">>),
%%
serial_number = maps:get(<<"serial_number">>, TerminalInfo, <<"">>),
%%
name = maps:get(<<"name">>, TerminalInfo, <<"">>),
%%
code = maps:get(<<"code">>, TerminalInfo, <<"">>),
%%
access_protocol = maps:get(<<"access_protocol">>, TerminalInfo, <<"">>),
%% ID
product_id = maps:get(<<"product_id">>, TerminalInfo, 0),
%% ID
vendor_id = maps:get(<<"vendor_id">>, TerminalInfo, 0),
%%
model = maps:get(<<"model">>, TerminalInfo, <<"">>),
%% ID
cell_id = maps:get(<<"cell_id">>, TerminalInfo, 0),
%%
status = ?TERMINAL_STATUS_OFFLINE,
%% 线
update_ts = 0
}.
%%
valid_terminal_info(#{<<"name">> := Name, <<"model">> := Model, <<"cell_id">> := CellId, <<"serial_number">> := SerialNumber}) ->
Name =/= <<"">> andalso Model =/= <<"">> andalso CellId > 0 andalso SerialNumber =/= <<"">>.
%%
terminal_error(M) when is_map(M) ->
if
not is_map_key(<<"name">>, M) orelse map_get(<<"name">>, M) == <<>> ->
<<"name is empty">>;
not is_map_key(<<"model">>, M) orelse map_get(<<"model">>, M) == <<>> ->
<<"model is empty">>;
not is_map_key(<<"serial_number">>, M) orelse map_get(<<"serial_number">>, M) == <<>> ->
<<"serial_number is empty">>;
true ->
<<"unknown error">>
end.

View File

@ -11,14 +11,13 @@
-include("iot.hrl").
%% API
-export([generate/1]).
-export([generate/1, new_terminal_id/0]).
%% id
-spec generate(Name :: string()) -> {ok, Id :: integer()} | {error, Reason :: any()}.
generate(Name) when is_list(Name) ->
case mnesia:transaction(fun() -> mnesia:dirty_update_counter(id_generator, Name, 1) end) of
{atomic, Id} ->
{ok, Id};
{aborted, Reason} ->
{error, Reason}
end.
-spec generate(Name :: atom()) -> Id :: integer().
generate(Name) when is_atom(Name) ->
mnesia:dirty_update_counter(id_generator, Name, 1).
-spec new_terminal_id() -> Id :: integer().
new_terminal_id() ->
generate(terminal).

View File

@ -14,8 +14,17 @@
-define(TAB_NAME, terminal).
%% API
-export([get_all_terminals/0, get_host_terminals/1, get_status_stat/0, table_size/0]).
-export([change_status/2, delete/1, to_map/1, add_terminal/1]).
-export([get_terminal/1, get_all_terminals/0, get_host_terminals/1, find_terminals/3, get_stat/0, table_size/0, match_spec/1]).
-export([change_status/2, delete/1, to_map/1, add_terminal/1, update_terminal/2]).
-spec get_terminal(TerminalId :: integer()) -> {ok, #terminal{}} | undefined.
get_terminal(TerminalId) when is_integer(TerminalId) ->
case mnesia:dirty_read(?TAB_NAME, TerminalId) of
[Terminal] ->
{ok, Terminal};
_ ->
undefined
end.
%% app信息
-spec get_all_terminals() -> {ok, Terminals :: [#terminal{}]} | {error, Reason :: any()}.
@ -44,14 +53,30 @@ get_host_terminals(HostId) when is_binary(HostId) ->
{error, Error}
end.
%% app信息
-spec find_terminals(Matches :: [{Name :: atom(), Val :: any()}], Start :: integer(), Limit :: integer()) ->
{ok, Terminals :: list(), TotalNum :: integer()} |
{error, Reason :: any()}.
find_terminals(Matches, Start, Limit) when is_list(Matches), is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
MatchSpec = terminal_model:match_spec(Matches),
Terminals0 = mnesia:dirty_select(?TAB_NAME, [MatchSpec]),
Terminals = sort(Terminals0),
Len = length(Terminals),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(Terminals, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end.
%%
-spec get_status_stat() -> {ok, Stat :: #{}} | {error, Reason :: any()}.
get_status_stat() ->
-spec get_stat() -> {ok, {Total :: integer(), Stat :: #{}}} | {error, Reason :: any()}.
get_stat() ->
Fun = fun() ->
mnesia:foldl(fun(#terminal{status = Status}, Acc) ->
mnesia:foldl(fun(#terminal{status = Status}, {Total, Acc}) ->
Num = maps:get(Status, Acc, 0),
Acc#{Status => Num + 1}
end, #{}, ?TAB_NAME)
{Total + 1, Acc#{Status => Num + 1}}
end, {0, #{}}, ?TAB_NAME)
end,
case mnesia:transaction(Fun) of
@ -70,6 +95,46 @@ add_terminal(Terminal = #terminal{}) ->
{error, Error}
end.
-spec change_status(TerminalId :: integer(), Fields :: #{}) -> ok | {error, Reason :: any()}.
update_terminal(TerminalId, Fields) when is_integer(TerminalId), is_map(Fields) ->
Fun = fun() ->
case mnesia:read(?TAB_NAME, TerminalId) of
[] ->
mnesia:abort(<<"terminal not found">>);
[Terminal] ->
NTerminal = lists:foldl(fun(E, Terminal0) ->
case E of
{<<"name">>, Name} when is_binary(Name) ->
Terminal0#terminal{name = Name};
{<<"serial_number">>, SerialNumber} when is_binary(SerialNumber) ->
Terminal0#terminal{serial_number = SerialNumber};
{<<"code">>, Code} when is_binary(Code) ->
Terminal0#terminal{code = Code};
{<<"access_protocol">>, AccessProtocol} when is_binary(AccessProtocol) ->
Terminal0#terminal{access_protocol = AccessProtocol};
{<<"product_id">>, ProductId} when is_integer(ProductId) ->
Terminal0#terminal{product_id = ProductId};
{<<"vendor_id">>, VendorId} when is_integer(VendorId) ->
Terminal0#terminal{vendor_id = VendorId};
{<<"model">>, Model} when is_binary(Model) ->
Terminal0#terminal{model = Model};
{<<"cell_id">>, CellId} when is_integer(CellId) ->
Terminal0#terminal{cell_id = CellId};
{Name, _} ->
mnesia:abort(<<"invalid: ", Name/binary>>)
end
end, Terminal, maps:to_list(Fields)),
mnesia:write(?TAB_NAME, NTerminal, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec change_status(TerminalId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
change_status(TerminalId, Status) when is_integer(TerminalId), is_integer(Status) ->
Fun = fun() ->
@ -100,6 +165,24 @@ delete(TerminalId) when is_integer(TerminalId) ->
table_size() ->
mnesia:table_info(host, size).
%%
match_spec(Specs) when is_list(Specs) ->
MatchHead = #host{model = '$1', cell_id = '$2', _ = '_'},
Guard = guard(Specs),
Result = ['$_'],
{MatchHead, Guard, Result}.
guard(Specs) when is_list(Specs) ->
guard(Specs, []).
guard([], Guard) ->
Guard;
guard([{model, Model}|Tail], Guard) when Model =/= <<"">> ->
guard(Tail, [{'=:=', '$1', Model}|Guard]);
guard([{cell, CellId}|Tail], Guard) when CellId > 0 ->
guard(Tail, [{'=:=', '$2', CellId}|Guard]);
guard([_|Tail], Guard) ->
guard(Tail, Guard).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%