diff --git a/apps/iot/include/iot.hrl b/apps/iot/include/iot.hrl index d607d14..a3907f0 100644 --- a/apps/iot/include/iot.hrl +++ b/apps/iot/include/iot.hrl @@ -198,6 +198,29 @@ status = 0:: integer() }). +%% 工单管理 +-record(issue, { + issue_id :: integer(), + %% 工单名称 + name :: binary(), + %% 创建用户 + uid :: integer(), + %% 部署类型 + deploy_type :: integer(), + %% 关联的id, 微服务id或者场景id + assoc_id :: any(), + %% 主机的id列表 + hosts = [] :: list(), + %% 超时时间 + timeout = 0 :: integer(), + %% 部署结果 + results = [], + %% 创建时间 + create_ts = 0 :: integer(), + %% 工单状态 + status = 0 :: integer() +}). + -record(http_endpoint, { url = <<>> }). diff --git a/apps/iot/src/iot_issue.erl b/apps/iot/src/iot_issue.erl new file mode 100644 index 0000000..b980933 --- /dev/null +++ b/apps/iot/src/iot_issue.erl @@ -0,0 +1,99 @@ +%%%------------------------------------------------------------------- +%%% @author licheng5 +%%% @copyright (C) 2023, +%%% @doc +%%% +%%% @end +%%% Created : 10. 3月 2023 16:44 +%%%------------------------------------------------------------------- +-module(iot_issue). +-author("licheng5"). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, { + +}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc Spawns the server and registers the local name (unique) +-spec(start_link() -> + {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%% @private +%% @doc Initializes the server +-spec(init(Args :: term()) -> + {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | + {stop, Reason :: term()} | ignore). +init([]) -> + {ok, #state{}}. + +%% @private +%% @doc Handling call messages +-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, + State :: #state{}) -> + {reply, Reply :: term(), NewState :: #state{}} | + {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | + {noreply, NewState :: #state{}} | + {noreply, NewState :: #state{}, timeout() | hibernate} | + {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | + {stop, Reason :: term(), NewState :: #state{}}). +handle_call(_Request, _From, State = #state{}) -> + {reply, ok, State}. + +%% @private +%% @doc Handling cast messages +-spec(handle_cast(Request :: term(), State :: #state{}) -> + {noreply, NewState :: #state{}} | + {noreply, NewState :: #state{}, timeout() | hibernate} | + {stop, Reason :: term(), NewState :: #state{}}). +handle_cast(_Request, State = #state{}) -> + {noreply, State}. + +%% @private +%% @doc Handling all non call/cast messages +-spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> + {noreply, NewState :: #state{}} | + {noreply, NewState :: #state{}, timeout() | hibernate} | + {stop, Reason :: term(), NewState :: #state{}}). +handle_info(_Info, State = #state{}) -> + {noreply, State}. + +%% @private +%% @doc This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), + State :: #state{}) -> term()). +terminate(_Reason, _State = #state{}) -> + ok. + +%% @private +%% @doc Convert process state when code is changed +-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{}, + Extra :: term()) -> + {ok, NewState :: #state{}} | {error, Reason :: term()}). +code_change(_OldVsn, State = #state{}, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/apps/iot/src/iot_issue_sup.erl b/apps/iot/src/iot_issue_sup.erl new file mode 100644 index 0000000..0b71ae7 --- /dev/null +++ b/apps/iot/src/iot_issue_sup.erl @@ -0,0 +1,60 @@ +%%%------------------------------------------------------------------- +%%% @author licheng5 +%%% @copyright (C) 2023, +%%% @doc +%%% +%%% @end +%%% Created : 10. 3月 2023 16:44 +%%%------------------------------------------------------------------- +-module(iot_issue_sup). +-author("licheng5"). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% API functions +%%%=================================================================== + +%% @doc Starts the supervisor +-spec(start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== + +%% @private +%% @doc Whenever a supervisor is started using supervisor:start_link/[2,3], +%% this function is called by the new process to find out about +%% restart strategy, maximum restart frequency and child +%% specifications. +-spec(init(Args :: term()) -> + {ok, {SupFlags :: {RestartStrategy :: supervisor:strategy(), + MaxR :: non_neg_integer(), MaxT :: non_neg_integer()}, + [ChildSpec :: supervisor:child_spec()]}} + | ignore | {error, Reason :: term()}). +init([]) -> + SupFlags = #{strategy => simple_one_for_one, intensity => 1000, period => 3600}, + AChild = #{ + id => 'iot_issue', + start => {'iot_issue', start_link, []}, + restart => temporary, + shutdown => 2000, + type => worker, + modules => ['iot_issue'] + }, + + {ok, {SupFlags, [AChild]}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/apps/iot/src/iot_mnesia.erl b/apps/iot/src/iot_mnesia.erl index 928b899..82e7f3a 100644 --- a/apps/iot/src/iot_mnesia.erl +++ b/apps/iot/src/iot_mnesia.erl @@ -64,6 +64,30 @@ init_database() -> {type, ordered_set} ]), + %% 应用场景 + mnesia:create_table(scenario, [ + {attributes, record_info(fields, scenario)}, + {record_name, scenario}, + {disc_copies, [node()]}, + {type, ordered_set} + ]), + + %% 应用场景部署关系表 + mnesia:create_table(scenario_deploy, [ + {attributes, record_info(fields, scenario_deploy)}, + {record_name, scenario_deploy}, + {disc_copies, [node()]}, + {type, ordered_set} + ]), + + %% 工单表 + mnesia:create_table(issue, [ + {attributes, record_info(fields, issue)}, + {record_name, issue}, + {disc_copies, [node()]}, + {type, ordered_set} + ]), + ok. %% 加入集群 @@ -85,4 +109,7 @@ copy_database(MasterNode) when is_atom(MasterNode) -> mnesia:add_table_copy(host, node(), ram_copies), mnesia:add_table_copy(terminal, node(), ram_copies), mnesia:add_table_copy(service, node(), ram_copies), + mnesia:add_table_copy(scenario, node(), ram_copies), + mnesia:add_table_copy(scenario_deploy, node(), ram_copies), + mnesia:add_table_copy(issue, node(), ram_copies), ok. \ No newline at end of file diff --git a/apps/iot/src/iot_sup.erl b/apps/iot/src/iot_sup.erl index 924e656..3cb80b4 100644 --- a/apps/iot/src/iot_sup.erl +++ b/apps/iot/src/iot_sup.erl @@ -37,6 +37,15 @@ init([]) -> modules => ['iot_router_sup'] }, + #{ + id => 'iot_issue_sup', + start => {'iot_issue_sup', start_link, []}, + restart => permanent, + shutdown => 2000, + type => supervisor, + modules => ['iot_issue_sup'] + }, + #{ id => 'iot_emqtt_client', start => {'iot_emqtt_client', start_link, []}, diff --git a/apps/iot/src/model/issue_model.erl b/apps/iot/src/model/issue_model.erl new file mode 100644 index 0000000..7ec209e --- /dev/null +++ b/apps/iot/src/model/issue_model.erl @@ -0,0 +1,109 @@ +%%%------------------------------------------------------------------- +%%% @author licheng5 +%%% @copyright (C) 2021, +%%% @doc +%%% +%%% @end +%%% Created : 27. 4月 2021 下午4:38 +%%%------------------------------------------------------------------- +-module(issue_model). +-author("licheng5"). +-include("iot.hrl"). +-include_lib("stdlib/include/qlc.hrl"). + +-define(TAB_NAME, issue). + +%% API +-export([get_issue/1, get_issues/3, add_issue/1, change_status/2, delete/1, table_size/0, get_user_issues/3]). +-export([to_map/1]). + +get_issue(IssueId) when is_integer(IssueId) -> + case mnesia:dirty_read(?TAB_NAME, IssueId) of + [Issue = #issue{}] -> + {ok, Issue}; + _ -> + undefined + end. + +%% 获取app信息 +-spec get_issues(Filter :: any(), Start :: integer(), Limit :: integer()) -> + {ok, Items :: list(), TotalNum :: integer()} | + {error, Reason :: any()}. +get_issues(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 -> + Items = mnesia:dirty_select(?TAB_NAME, [Spec]), + NItems = lists:sublist(Items, Start + 1, Limit), + {ok, NItems, length(Items)}. + +-spec get_user_issues(UserId :: integer(), Start :: integer(), Limit :: integer()) -> + {ok, Items :: [#host{}], Num :: integer()} | + {error, Reason :: any()}. +get_user_issues(UserId, Start, Limit) when is_integer(UserId), is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 -> + Fun = fun() -> + Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#issue.uid =:= UserId]), + qlc:e(Q) + end, + case mnesia:transaction(Fun) of + {atomic, Items} when is_list(Items) -> + {ok, lists:sublist(Items, Start + 1, Limit), length(Items)}; + {aborted, Error} -> + {error, Error} + end. + +-spec add_issue(Issue :: #issue{}) -> ok | {error, Reason :: any()}. +add_issue(Issue = #issue{}) -> + case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Issue, write) end) of + {atomic, _} -> + ok; + {aborted, Error} -> + {error, Error} + end. + +-spec change_status(IssueId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}. +change_status(IssueId, Status) when is_integer(IssueId), is_integer(Status) -> + Fun = fun() -> + case mnesia:read(?TAB_NAME, IssueId) of + [] -> + mnesia:abort(<<"issue not found">>); + [Issue] -> + mnesia:write(?TAB_NAME, Issue#issue{status = Status}, write) + end + end, + case mnesia:transaction(Fun) of + {atomic, ok} -> + ok; + {aborted, Reason} -> + {error, Reason} + end. + +-spec delete(IssueId :: binary()) -> ok | {error, Reason :: any()}. +delete(IssueId) when is_integer(IssueId) -> + case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, IssueId, write) end) of + {atomic, ok} -> + ok; + {aborted, Reason} -> + {error, Reason} + end. + +%% 获取app表的数据大小 +table_size() -> + mnesia:table_info(?TAB_NAME, size). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% helper methods +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +to_map(#issue{issue_id = IssueId, name = Name, uid = Uid, deploy_type = DeployType, assoc_id = AssocId, hosts = Hosts, timeout = Timeout, + create_ts = CreateTs, results = Results, status = Status}) -> + + #{ + <<"issue_id">> => IssueId, + <<"name">> => Name, + <<"uid">> => Uid, + <<"deploy_type">> => DeployType, + <<"assoc_id">> => AssocId, + <<"hosts">> => Hosts, + <<"timeout">> => Timeout, + <<"status">> => Status, + <<"create_ts">> => CreateTs, + <<"results">> => Results + }. \ No newline at end of file