merge master
This commit is contained in:
parent
79e7125fb8
commit
ac27280299
@ -9,256 +9,15 @@
|
||||
-author("licheng5").
|
||||
|
||||
%% 主机状态定义
|
||||
-define(HOST_STATUS_INACTIVE, 0).
|
||||
-define(HOST_STATUS_ONLINE, 1).
|
||||
-define(HOST_STATUS_OFFLINE, 2).
|
||||
-define(HOST_STATUS_DELETE, 3).
|
||||
-define(HOST_STATUS_INACTIVE, -1). %% 未接入
|
||||
-define(HOST_STATUS_OFFLINE, 0). %% 离线
|
||||
-define(HOST_STATUS_ONLINE, 1). %% 在线
|
||||
|
||||
%% 终端状态
|
||||
-define(TERMINAL_STATUS_OFFLINE, 0).
|
||||
-define(TERMINAL_STATUS_ONLINE, 1).
|
||||
%% 下发的任务状态
|
||||
-define(TASK_STATUS_INIT, -1). %% 未接入
|
||||
-define(TASK_STATUS_FAILED, 0). %% 离线
|
||||
-define(TASK_STATUS_OK, 1). %% 在线
|
||||
|
||||
%% cpu相关
|
||||
-record(cpu_metric, {
|
||||
%% cpu编号
|
||||
num = 1,
|
||||
%% 负载
|
||||
load = 0
|
||||
}).
|
||||
|
||||
%% 内存相关, 单位kb
|
||||
-record(memory_metric, {
|
||||
%% 使用量
|
||||
used = 0,
|
||||
%% 空余
|
||||
free = 0,
|
||||
%% 总量
|
||||
total = 0
|
||||
}).
|
||||
|
||||
%% 硬盘相关, 单位kb
|
||||
-record(disk_metric, {
|
||||
%% 使用量
|
||||
used = 0,
|
||||
%% 空余
|
||||
free = 0,
|
||||
%% 总量
|
||||
total = 0
|
||||
}).
|
||||
|
||||
%% 接口相关
|
||||
-record(interface_metric, {
|
||||
%% 接口名称
|
||||
name = <<>>,
|
||||
%% 接口描述
|
||||
desc = <<>>,
|
||||
%% 接口详情, 可以是元素的json
|
||||
detail = <<>>,
|
||||
%% 接口状态
|
||||
status = 0
|
||||
}).
|
||||
|
||||
%% 微服务指标收集
|
||||
-record(service_metric, {
|
||||
%% 标识
|
||||
symbol :: binary(),
|
||||
%% 名称
|
||||
name :: binary(),
|
||||
%% 当前最新值
|
||||
last_value = 0 :: number(),
|
||||
%% 单位, 如电压: a、v
|
||||
unit :: any(),
|
||||
%% 历史值序列, 队列;保存最近的100数值, 格式为: [{ts, value}]
|
||||
queue = queue:new() :: queus:queue(),
|
||||
%% 最后更新时间
|
||||
update_ts = 0 :: integer()
|
||||
}).
|
||||
|
||||
%% 主机指标, 主机和指标之间是一对一的关系,可以分离出新的表
|
||||
-record(host_metric, {
|
||||
%% cpu相关
|
||||
cpus = [],
|
||||
%% cpu温度
|
||||
cpu_temperature = 0,
|
||||
%% 内存状态
|
||||
memory = #memory_metric{},
|
||||
%% 硬盘状态
|
||||
disk = #disk_metric{},
|
||||
%% 接口状态
|
||||
interfaces = []
|
||||
}).
|
||||
|
||||
%% id生成器
|
||||
-record(id_generator, {
|
||||
name :: atom(),
|
||||
seq = 0 :: integer()
|
||||
}).
|
||||
|
||||
%% 主机定义
|
||||
-record(host, {
|
||||
%% ID
|
||||
host_id :: binary(),
|
||||
%% 序列号
|
||||
serial_number = <<>> :: binary(),
|
||||
%% 名称
|
||||
name = <<>> :: binary(),
|
||||
%% 型号
|
||||
model = <<>> :: binary(),
|
||||
%% 单元网格编号
|
||||
cell_id :: integer(),
|
||||
%% rsa公钥
|
||||
public_rsa = <<>> :: binary(),
|
||||
%% aes的key, 后续通讯需要基于这个加密
|
||||
aes = <<>> :: binary(),
|
||||
metric = #host_metric{},
|
||||
%% 接入时间
|
||||
activated_ts = 0 :: integer(),
|
||||
%% 最后上线时间
|
||||
update_ts = 0 :: integer(),
|
||||
%% 主机状态
|
||||
status = ?HOST_STATUS_INACTIVE :: integer()
|
||||
}).
|
||||
|
||||
%% 终端设备表
|
||||
-record(terminal, {
|
||||
%% 设备ID
|
||||
terminal_id :: integer(),
|
||||
%% 关联主机
|
||||
host_id :: binary(),
|
||||
%% 序列号
|
||||
serial_number = <<>> :: binary(),
|
||||
%% 名称
|
||||
name :: binary(),
|
||||
%% 设备编码
|
||||
code :: binary(),
|
||||
%% 接入协议
|
||||
access_protocol :: binary(),
|
||||
%% 产品ID,枚举类型
|
||||
product_id = 0 :: integer(),
|
||||
%% 厂商ID,枚举类型
|
||||
vendor_id = 0 :: integer(),
|
||||
%% 型号
|
||||
model = <<>> :: binary(),
|
||||
%% 所在单元ID,管理系统负责
|
||||
cell_id = 0 :: integer(),
|
||||
%% 终端状态
|
||||
status = 0 :: integer(),
|
||||
%% 最后上线时间
|
||||
update_ts = 0 :: integer()
|
||||
}).
|
||||
|
||||
%% 微服务表
|
||||
-record(service, {
|
||||
%% ID, 基于UUID生成
|
||||
service_id :: binary(),
|
||||
%% 关联主机
|
||||
host_id :: binary(),
|
||||
%% 名称
|
||||
name :: binary(),
|
||||
%% 类型
|
||||
category :: binary(),
|
||||
%% 应用对象
|
||||
apply_object :: binary(),
|
||||
%% 版本
|
||||
version :: binary(),
|
||||
%% 执行次数
|
||||
execute_count = 0,
|
||||
%% 部署时间
|
||||
deploy_ts = 0 :: integer(),
|
||||
%% 指标, 终端指标涉及到查询效率和更新效率的问题,因此合并到一起
|
||||
metrics = [] :: [#service_metric{}],
|
||||
status = 0
|
||||
}).
|
||||
|
||||
%% scenario 应用场景
|
||||
-record(scenario, {
|
||||
%% 场景id,自增id
|
||||
scenario_id :: integer(),
|
||||
%% 名称
|
||||
name :: binary(),
|
||||
%% 场景描述
|
||||
desc :: binary(),
|
||||
%% 场景规则数据
|
||||
rule :: binary(),
|
||||
%% 最后更新时间
|
||||
update_ts = 0 :: integer(),
|
||||
%% 状态
|
||||
status :: integer()
|
||||
}).
|
||||
|
||||
%% 场景部署关系表
|
||||
-record(scenario_deploy, {
|
||||
%% id,自增id
|
||||
deploy_id :: integer(),
|
||||
%% 场景id
|
||||
scenario_id :: integer(),
|
||||
%% 主机id
|
||||
host_id :: binary(),
|
||||
%% 创建时间
|
||||
create_ts = 0 :: integer(),
|
||||
%% 更新时间
|
||||
update_ts = 0 :: integer(),
|
||||
%% 状态
|
||||
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 = <<>>
|
||||
}).
|
||||
|
||||
-record(kafka_endpoint, {
|
||||
|
||||
}).
|
||||
|
||||
%% 数据转换规则表
|
||||
-record(router, {
|
||||
router_id :: integer(),
|
||||
%% 名称
|
||||
name = <<>>,
|
||||
%% 数据过滤规则
|
||||
rule = <<>>,
|
||||
%% 对端
|
||||
endpoint,
|
||||
%% 状态
|
||||
status = 0
|
||||
}).
|
||||
|
||||
%% 设备分类
|
||||
-define(DEVICE_HOST, 1).
|
||||
-define(DEVICE_TERMINAL, 2).
|
||||
|
||||
%% 操作日志表
|
||||
-record(log, {
|
||||
log_id :: integer(),
|
||||
%% 操作名称名称
|
||||
action_name = <<>>,
|
||||
%% 设备分类名称
|
||||
device_type :: integer(),
|
||||
%% 关联ID
|
||||
assoc_id :: term(),
|
||||
%% 创建时间
|
||||
create_ts = 0 :: integer()
|
||||
router_id
|
||||
}).
|
||||
17
apps/iot/src/database/device_bo.erl
Normal file
17
apps/iot/src/database/device_bo.erl
Normal file
@ -0,0 +1,17 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 16. 5月 2023 12:48
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(device_bo).
|
||||
-author("aresei").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([get_device_by_uuid/1]).
|
||||
|
||||
get_device_by_uuid(UUID) when is_binary(UUID) ->
|
||||
mysql_client:get_row(<<"SELECT * FROM device WHERE uuid = ? LIMIT 1">>, [UUID]).
|
||||
46
apps/iot/src/database/host_bo.erl
Normal file
46
apps/iot/src/database/host_bo.erl
Normal file
@ -0,0 +1,46 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 16. 5月 2023 12:48
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(host_bo).
|
||||
-author("aresei").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([get_all_hosts/0, change_status/2, is_authorized/1, get_host/1, get_host_by_uuid/1, create_host/1]).
|
||||
|
||||
-spec get_all_hosts() -> UUIDList :: [binary()].
|
||||
get_all_hosts() ->
|
||||
case mysql_client:get_all(<<"SELECT uuid FROM host where uuid != ''">>) of
|
||||
{ok, Hosts} ->
|
||||
lists:map(fun(#{<<"uuid">> := UUID}) -> UUID end, Hosts);
|
||||
{error, _Reason} ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_host(HostId) when is_integer(HostId) ->
|
||||
mysql_client:get_row(<<"SELECT * FROM host WHERE id = ? LIMIT 1">>, [HostId]).
|
||||
|
||||
get_host_by_uuid(UUID) when is_binary(UUID) ->
|
||||
mysql_client:get_row(<<"SELECT * FROM host WHERE uuid = ? LIMIT 1">>, [UUID]).
|
||||
|
||||
create_host(UUID) when is_binary(UUID) ->
|
||||
mysql_client:insert(<<"host">>, #{<<"UUID">> => UUID, <<"status">> => ?HOST_STATUS_INACTIVE}, true).
|
||||
|
||||
%% 修改主机的状态
|
||||
-spec change_status(UUID :: binary(), Status :: integer()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
|
||||
change_status(UUID, Status) when is_binary(UUID), is_integer(Status) ->
|
||||
mysql_client:update_by(<<"UPDATE host SET status = ? WHERE uuid = ? LIMIT 1">>, [Status, UUID]).
|
||||
|
||||
%% 判断主机是否已经授权
|
||||
is_authorized(HostId) when is_integer(HostId) ->
|
||||
case mysql_client:get_row(<<"SELECT * FROM host WHERE id = ? LIMIT 1">>, [HostId]) of
|
||||
{ok, #{<<"status">> := Status}} when Status =/= ?HOST_STATUS_INACTIVE ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
19
apps/iot/src/database/task_logs_bo.erl
Normal file
19
apps/iot/src/database/task_logs_bo.erl
Normal file
@ -0,0 +1,19 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 16. 5月 2023 12:48
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(task_logs_bo).
|
||||
-author("aresei").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([change_status/2]).
|
||||
|
||||
%% 修改主机的状态
|
||||
-spec change_status(TaskId :: integer(), Status :: integer()) -> {ok, AffectedRow :: integer()} | {error, Reason :: any()}.
|
||||
change_status(TaskId, Status) when is_integer(TaskId), is_integer(Status) ->
|
||||
mysql_client:update_by(<<"UPDATE task_logs SET status = ? WHERE id = ? LIMIT 1">>, [Status, TaskId]).
|
||||
@ -17,131 +17,103 @@
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%% 添加主机
|
||||
handle_request("POST", "/host/create", _, HostInfo) ->
|
||||
lager:debug("[host_handler] create post params: ~p", [HostInfo]),
|
||||
|
||||
case convert_host_info(HostInfo) of
|
||||
{ok, Host} ->
|
||||
HostId = iot_util:uuid(),
|
||||
case host_model:add_host(Host#host{host_id = HostId}) of
|
||||
ok ->
|
||||
{ok, 200, iot_util:json_data(HostId)};
|
||||
{error, Reason} when is_binary(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;
|
||||
{error, Reason} ->
|
||||
{ok, 200, iot_util:json_error(-1, Reason)}
|
||||
end;
|
||||
|
||||
%% 批量导入
|
||||
handle_request("POST", "/host/batch_import", _, HostInfos) ->
|
||||
lager:debug("[host_handler] batch_create post params: ~p", [HostInfos]),
|
||||
|
||||
{SuccessList, FailedList} = lists:foldl(fun(HostInfo, {SuccessList0, FailedList0}) ->
|
||||
case convert_host_info(HostInfo) of
|
||||
{ok, Host = #host{serial_number = SerialNumber}} ->
|
||||
HostId = iot_util:uuid(),
|
||||
case host_model:add_host(Host#host{host_id = HostId}) of
|
||||
ok ->
|
||||
SuccessItem = #{<<"serial_number">> => SerialNumber, <<"host_id">> => HostId},
|
||||
{[SuccessItem|SuccessList0], FailedList0};
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
ErrorItem = maps:put(<<"error_message">>, Reason, HostInfo),
|
||||
{SuccessList0, [ErrorItem|FailedList0]}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
ErrorItem = maps:put(<<"error_message">>, Reason, HostInfo),
|
||||
{SuccessList0, [ErrorItem|FailedList0]}
|
||||
end
|
||||
end, {[], []}, HostInfos),
|
||||
ImportResult = #{
|
||||
<<"success_list">> => lists:reverse(SuccessList),
|
||||
<<"failed_list">> => lists:reverse(FailedList)
|
||||
},
|
||||
|
||||
{ok, 200, iot_util:json_data(ImportResult)};
|
||||
|
||||
handle_request(_, "/host/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,
|
||||
|
||||
MatchSpec = host_model:match_spec([{model, Model}, {cell, CellId1}]),
|
||||
case host_model:get_hosts(MatchSpec, Start, Size) of
|
||||
{ok, Hosts, TotalNum} ->
|
||||
Response = #{
|
||||
<<"hosts">> => lists:map(fun host_model:to_map/1, Hosts),
|
||||
<<"stat">> => host_model:get_stat(),
|
||||
<<"total_num">> => TotalNum
|
||||
},
|
||||
|
||||
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", "/host/detail", #{<<"id">> := HostId}, _) ->
|
||||
lager:debug("[host_handler] detail id is: ~p", [HostId]),
|
||||
case host_model:get_host(HostId) of
|
||||
handle_request("GET", "/host/metric", #{<<"uuid">> := UUID}, _) ->
|
||||
lager:debug("[host_handler] get host metric uuid is: ~p", [UUID]),
|
||||
case iot_host:get_pid(UUID) of
|
||||
undefined ->
|
||||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||||
{ok, Host} ->
|
||||
HostInfo = host_model:to_map(Host),
|
||||
%% 获取终端信息
|
||||
{ok, Terminals0} = terminal_model:get_host_terminals(HostId),
|
||||
Terminals = lists:map(fun terminal_model:to_map/1, Terminals0),
|
||||
HostInfo1 = maps:put(<<"terminals">>, Terminals, HostInfo),
|
||||
%% 获取微服务信息
|
||||
{ok, Services0} = service_model:get_host_services(HostId),
|
||||
Services = lists:map(fun service_model:to_map/1, Services0),
|
||||
HostInfo2 = maps:put(<<"services">>, Services, HostInfo1),
|
||||
|
||||
%% 获取部署应用场景
|
||||
{ok, DeployList0} = scenario_deploy_model:get_host_deploy_list(HostId),
|
||||
DeployList = lists:map(fun scenario_deploy_model:to_map/1, DeployList0),
|
||||
%% 获取部署的场景信息
|
||||
DeployList1 = lists:map(fun(DeployInfo = #{<<"scenario_id">> := ScenarioId}) ->
|
||||
case scenario_model:get_scenario(ScenarioId) of
|
||||
{ok, Scenario} ->
|
||||
ScenarioInfo = scenario_model:to_map(Scenario),
|
||||
DeployInfo#{<<"scenario_info">> => ScenarioInfo};
|
||||
undefined ->
|
||||
DeployInfo#{<<"scenario_info">> => #{}}
|
||||
end
|
||||
end, DeployList),
|
||||
|
||||
HostInfo3 = maps:put(<<"deploy_list">>, DeployList1, HostInfo2),
|
||||
|
||||
{ok, 200, iot_util:json_data(HostInfo3)}
|
||||
Pid when is_pid(Pid) ->
|
||||
{ok, MetricInfo} = iot_host:get_metric(Pid),
|
||||
case map_size(MetricInfo) > 0 of
|
||||
true ->
|
||||
{ok, 200, iot_util:json_data(MetricInfo)};
|
||||
false ->
|
||||
{ok, 200, iot_util:json_error(404, <<"no metric info">>)}
|
||||
end
|
||||
end;
|
||||
|
||||
handle_request("POST", "/host/update", _, Fields0 = #{<<"host_id">> := HostId}) when is_binary(HostId) ->
|
||||
lager:debug("[host_handler] post params is: ~p", [Fields0]),
|
||||
%% 重新加载对应的主机信息
|
||||
handle_request("POST", "/host/reload", _, #{<<"uuid">> := UUID}) when is_binary(UUID) ->
|
||||
lager:debug("[host_handler] will reload host uuid: ~p", [UUID]),
|
||||
case iot_host_sup:ensured_host_started(UUID) of
|
||||
{ok, Pid} when is_pid(Pid) ->
|
||||
lager:debug("[host_handler] already_started reload host uuid: ~p, success", [UUID]),
|
||||
case iot_host:reload(Pid) of
|
||||
ok ->
|
||||
{ok, 200, iot_util:json_data(<<"success">>)};
|
||||
{error, ReloadError} ->
|
||||
lager:debug("[host_handler] reload host uuid: ~p, error: ~p", [UUID, ReloadError]),
|
||||
{ok, 200, iot_util:json_error(400, <<"reload error">>)}
|
||||
end;
|
||||
Error ->
|
||||
lager:debug("[host_handler] reload host uuid: ~p, error: ~p", [UUID, Error]),
|
||||
{ok, 200, iot_util:json_error(404, <<"reload error">>)}
|
||||
end;
|
||||
|
||||
Fields = maps:remove(<<"host_id">>, Fields0),
|
||||
case host_model:update_host(HostId, Fields) of
|
||||
%% 删除对应的主机信息
|
||||
handle_request("POST", "/host/delete", _, #{<<"uuid">> := UUID}) when is_binary(UUID) ->
|
||||
lager:debug("[host_handler] will delete host uuid: ~p", [UUID]),
|
||||
|
||||
case iot_host_sup:delete_host(UUID) of
|
||||
ok ->
|
||||
{ok, 200, iot_util:json_data(<<"success">>)};
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(-1, Reason)}
|
||||
{error, Reason} ->
|
||||
lager:debug("[host_handler] delete host uuid: ~p, get error is: ~p", [UUID, Reason]),
|
||||
{ok, 200, iot_util:json_error(404, <<"error">>)}
|
||||
end;
|
||||
|
||||
%% 下发参数
|
||||
handle_request("POST", "/host/publish_command", _,
|
||||
PostParams = #{<<"uuid">> := UUID, <<"command_type">> := CommandType, <<"task_id">> := TaskId, <<"timeout">> := Timeout, <<"params">> := Params})
|
||||
when is_binary(UUID), is_integer(TaskId), is_integer(Timeout), is_integer(CommandType) ->
|
||||
|
||||
lager:debug("[http_host_handler] publish_command body is: ~p", [PostParams]),
|
||||
case iot_host:get_pid(UUID) of
|
||||
undefined ->
|
||||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||||
Pid when is_pid(Pid) ->
|
||||
Reply = #{
|
||||
<<"t_id">> => integer_to_binary(TaskId),
|
||||
<<"t">> => Timeout,
|
||||
<<"ts">> => iot_util:current_time(),
|
||||
<<"m">> => Params
|
||||
},
|
||||
BinReply = jiffy:encode(append_service_name(PostParams, Reply), [force_utf8]),
|
||||
|
||||
case iot_host:build_command(Pid, CommandType, BinReply) of
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
task_logs_bo:change_status(TaskId, ?TASK_STATUS_FAILED),
|
||||
{ok, 200, iot_util:json_error(400, Reason)};
|
||||
{ok, Topic, BinCommand} ->
|
||||
case iot_mqtt_publisher:publish(Topic, BinCommand, 2) of
|
||||
{ok, Ref} ->
|
||||
receive
|
||||
{ok, Ref, _PacketId} ->
|
||||
{ok, _} = task_logs_bo:change_status(TaskId, ?TASK_STATUS_OK),
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
after Timeout * 1000 ->
|
||||
lager:debug("[iot_host_handler] host_id uuid: ~p, publish topic success, but get ack timeout", [UUID]),
|
||||
{ok, _} = task_logs_bo:change_status(TaskId, ?TASK_STATUS_FAILED),
|
||||
{ok, 200, iot_util:json_error(401, <<"命令执行超时, 请重试"/utf8>>)}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_host] host_id uuid: ~p, publish topic get error: ~p", [UUID, Reason]),
|
||||
{ok, _} = task_logs_bo:change_status(TaskId, ?TASK_STATUS_FAILED),
|
||||
{ok, 200, iot_util:json_error(402, <<"发送命令到mqtt服务失败"/utf8>>)}
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
%% 处理主机的授权, 激活处理
|
||||
handle_request("POST", "/host/activate", _, #{<<"uuid">> := UUID}) when is_binary(UUID) ->
|
||||
case iot_host_sup:ensured_host_started(UUID) of
|
||||
{error, Reason} ->
|
||||
lager:debug("[host_handler] activate host_id: ~p, failed with reason: ~p", [UUID, Reason]),
|
||||
{ok, 200, iot_util:json_error(-1, <<"host not found">>)};
|
||||
{ok, Pid} when is_pid(Pid) ->
|
||||
lager:debug("[host_handler] reload host_id: ~p, success", [UUID]),
|
||||
ok = iot_host:activate(Pid),
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
end;
|
||||
|
||||
handle_request(_, Path, _, _) ->
|
||||
@ -152,31 +124,8 @@ handle_request(_, Path, _, _) ->
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
convert_host_info(#{<<"name">> := Name, <<"model">> := Model, <<"cell_id">> := CellId, <<"serial_number">> := SerialNumber}) ->
|
||||
CheckResult = if
|
||||
Name == <<>> ->
|
||||
{error, <<"name is empty">>};
|
||||
Model == <<>> ->
|
||||
{error, <<"model is empty">>};
|
||||
SerialNumber == <<>> ->
|
||||
{error, <<"serial_number is empty">>};
|
||||
not is_integer(CellId) orelse CellId < 0 ->
|
||||
{ok, <<"cell_id is error">>};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
|
||||
case CheckResult of
|
||||
ok ->
|
||||
{ok, #host{
|
||||
serial_number = SerialNumber,
|
||||
name = Name,
|
||||
model = Model,
|
||||
cell_id = CellId,
|
||||
status = ?HOST_STATUS_INACTIVE
|
||||
}};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
convert_host_info(_) ->
|
||||
{error, <<"miss required param">>}.
|
||||
%% 追加service_name参数
|
||||
append_service_name(#{<<"serivce_name">> := ServiceName}, Reply) when is_binary(ServiceName), ServiceName =/= <<"">> ->
|
||||
Reply#{<<"to">> => ServiceName};
|
||||
append_service_name(_, Reply) ->
|
||||
Reply.
|
||||
@ -1,160 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 06. 3月 2023 14:29
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(http_iot_handler).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([handle_request/4]).
|
||||
|
||||
%% 下发参数
|
||||
handle_request("POST", "/iot/send_params", _, PostParams = #{<<"host_id">> := HostId, <<"service_name">> := ServiceName, <<"params">> := Params}) ->
|
||||
lager:debug("body is: ~p", [PostParams]),
|
||||
|
||||
case host_model:activate(HostId) of
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(404, Reason)};
|
||||
{ok, #host{aes = Aes}} ->
|
||||
Reply = #{
|
||||
<<"t">> => 1,
|
||||
<<"b">> => #{
|
||||
<<"t_id">> => iot_util:uuid() ,
|
||||
<<"to">> => ServiceName,
|
||||
<<"t">> => 10,
|
||||
<<"m">> => Params
|
||||
}
|
||||
},
|
||||
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
|
||||
|
||||
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
|
||||
lager:debug("enc_reply is: ~p", [EncMsg]),
|
||||
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
end;
|
||||
|
||||
%% 下发采集项
|
||||
handle_request("POST", "/iot/send_metrics", _,
|
||||
PostParams = #{<<"host_id">> := HostId, <<"service_name">> := ServiceName, <<"metrics">> := Metrics}) ->
|
||||
|
||||
lager:debug("body is: ~p", [PostParams]),
|
||||
|
||||
case host_model:activate(HostId) of
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(404, Reason)};
|
||||
{ok, #host{aes = Aes}} ->
|
||||
Reply = #{
|
||||
<<"t">> => 2,
|
||||
<<"b">> => #{
|
||||
<<"t_id">> => iot_util:uuid() ,
|
||||
<<"to">> => ServiceName,
|
||||
<<"t">> => 10,
|
||||
<<"m">> => Metrics
|
||||
}
|
||||
},
|
||||
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
|
||||
|
||||
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
|
||||
lager:debug("enc_reply is: ~p", [EncMsg]),
|
||||
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
end;
|
||||
|
||||
%% 下发微服务
|
||||
handle_request("POST", "/iot/send_mirco_service", _,
|
||||
PostParams = #{<<"host_id">> := HostId, <<"args">> := Args}) ->
|
||||
|
||||
lager:debug("body is: ~p", [PostParams]),
|
||||
|
||||
case host_model:activate(HostId) of
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(404, Reason)};
|
||||
{ok, #host{aes = Aes}} ->
|
||||
Reply = #{
|
||||
<<"t">> => 3,
|
||||
<<"b">> => #{
|
||||
<<"t_id">> => iot_util:uuid() ,
|
||||
<<"t">> => 10,
|
||||
<<"m">> => Args
|
||||
}
|
||||
},
|
||||
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
|
||||
|
||||
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
|
||||
lager:debug("enc_reply is: ~p", [EncMsg]),
|
||||
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
end;
|
||||
|
||||
%% 下发数据流图
|
||||
handle_request("POST", "/iot/send_data_flow", _,
|
||||
PostParams = #{<<"host_id">> := HostId, <<"args">> := Args}) ->
|
||||
|
||||
lager:debug("body is: ~p", [PostParams]),
|
||||
|
||||
case host_model:activate(HostId) of
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(404, Reason)};
|
||||
{ok, #host{aes = Aes}} ->
|
||||
Reply = #{
|
||||
<<"t">> => 4,
|
||||
<<"b">> => #{
|
||||
<<"t_id">> => iot_util:uuid() ,
|
||||
<<"t">> => 10,
|
||||
<<"m">> => Args
|
||||
}
|
||||
},
|
||||
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
|
||||
|
||||
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
|
||||
lager:debug("enc_reply is: ~p", [EncMsg]),
|
||||
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
end;
|
||||
|
||||
%% 处理命令下发
|
||||
handle_request("POST", "/iot/send_command", _, Params = #{<<"host_id">> := HostId}) ->
|
||||
lager:debug("body is: ~p", [Params]),
|
||||
|
||||
case host_model:activate(HostId) of
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(404, Reason)};
|
||||
{ok, #host{aes = Aes, public_rsa = PubKey}} ->
|
||||
Reply = #{
|
||||
<<"a">> => true,
|
||||
<<"aes">> => Aes,
|
||||
<<"reply">> => <<"client.reply.", HostId/binary>>
|
||||
},
|
||||
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
|
||||
|
||||
%% TODO 发送消息到终端
|
||||
lager:debug("enc_reply is: ~p", [EncReply]),
|
||||
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
end;
|
||||
|
||||
%% 处理主机的授权
|
||||
handle_request("POST", "/iot/host_auth", _, Params = #{<<"host_id">> := HostId}) ->
|
||||
lager:debug("body is: ~p", [Params]),
|
||||
|
||||
case host_model:activate(HostId) of
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
{ok, 200, iot_util:json_error(404, Reason)};
|
||||
{ok, #host{aes = Aes, public_rsa = PubKey}} ->
|
||||
Reply = #{
|
||||
<<"a">> => true,
|
||||
<<"aes">> => Aes,
|
||||
<<"reply">> => <<"client.reply.", HostId/binary>>
|
||||
},
|
||||
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
|
||||
|
||||
%% TODO 发送消息到终端
|
||||
lager:debug("enc_reply is: ~p", [EncReply]),
|
||||
|
||||
{ok, 200, iot_util:json_data(<<"success">>)}
|
||||
end.
|
||||
@ -1,41 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2020, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 26. 4月 2020 3:36 下午
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(http_log_handler).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([handle_request/4]).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
handle_request(_, "/log/list", GetParams, _PostParams) ->
|
||||
Size0 = maps:get(<<"size">>, GetParams, <<"10">>),
|
||||
Size = binary_to_integer(Size0),
|
||||
true = Size > 0,
|
||||
|
||||
case log_model:get_last_logs(Size) of
|
||||
{ok, Logs} ->
|
||||
LogInfos = lists:map(fun log_model:to_map/1, Logs),
|
||||
|
||||
{ok, 200, iot_util:json_data(LogInfos)};
|
||||
{error, Reason} ->
|
||||
lager:warning("[host_handler] get a error: ~p", [Reason]),
|
||||
{ok, 200, iot_util:json_error(-1, <<"database error">>)}
|
||||
end;
|
||||
|
||||
handle_request(_, Path, _, _) ->
|
||||
Path1 = list_to_binary(Path),
|
||||
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@ -1,64 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2020, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 26. 4月 2020 3:36 下午
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(http_router_handler).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([handle_request/4]).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
handle_request(_, "/router/list", Params, PostParams) ->
|
||||
Page0 = maps:get(<<"page">>, Params, <<"1">>),
|
||||
Size0 = maps:get(<<"size">>, Params, <<"10">>),
|
||||
Page = binary_to_integer(Page0),
|
||||
Size = binary_to_integer(Size0),
|
||||
|
||||
true = Page > 0 andalso Size > 0,
|
||||
Start = (Page - 1) * Size,
|
||||
|
||||
%% 处理查询条件
|
||||
Name = maps:get(<<"name">>, PostParams, <<"">>),
|
||||
|
||||
MatchSpec = router_model:match_spec([{name, Name}]),
|
||||
case router_model:get_routers(MatchSpec, Start, Size) of
|
||||
{ok, Routers, TotalNum} ->
|
||||
Response = #{
|
||||
<<"routers">> => lists:map(fun(R) -> router_model:to_map(R) end, Routers),
|
||||
<<"total_num">> => TotalNum
|
||||
},
|
||||
|
||||
{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", "/router/detail", #{<<"router_id">> := RouterId}, _) ->
|
||||
case router_model:get_router(RouterId) of
|
||||
undefined ->
|
||||
{ok, 200, iot_util:json_error(404, <<"router not found">>)};
|
||||
{ok, Router} ->
|
||||
RouterInfo = router_model:to_map(Router),
|
||||
|
||||
{ok, 200, iot_util:json_data(RouterInfo)}
|
||||
end;
|
||||
|
||||
handle_request("POST", "/router/changer_status", _, Params = #{<<"router_id">> := RouterId, <<"status">> := NStatus}) ->
|
||||
lager:debug("[router_handler] post params is: ~p", [Params]),
|
||||
|
||||
case router_model:change_status(RouterId, NStatus) of
|
||||
ok ->
|
||||
{ok, 200, iot_util:json_data(<<"success">>)};
|
||||
{error, _} ->
|
||||
{ok, 200, iot_util:json_error(404, <<"error">>)}
|
||||
end.
|
||||
@ -1,97 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2020, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 26. 4月 2020 3:36 下午
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(http_scenario_handler).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([handle_request/4]).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
handle_request(_, "/scenario/list", Params, PostParams) ->
|
||||
Page0 = maps:get(<<"page">>, Params, <<"1">>),
|
||||
Size0 = maps:get(<<"size">>, Params, <<"10">>),
|
||||
Page = binary_to_integer(Page0),
|
||||
Size = binary_to_integer(Size0),
|
||||
|
||||
true = Page > 0 andalso Size > 0,
|
||||
Start = (Page - 1) * Size,
|
||||
|
||||
%% 处理查询条件
|
||||
Name = maps:get(<<"name">>, PostParams, <<"">>),
|
||||
|
||||
MatchSpec = scenario_model:match_spec([{name, Name}]),
|
||||
case scenario_model:get_scenario_list(MatchSpec, Start, Size) of
|
||||
{ok, ScenarioList, TotalNum} ->
|
||||
Response = #{
|
||||
<<"scenarios">> => lists:map(fun scenario_model:to_map/1, ScenarioList),
|
||||
<<"total_num">> => TotalNum
|
||||
},
|
||||
{ok, 200, iot_util:json_data(Response)};
|
||||
{error, Reason} ->
|
||||
lager:warning("[http_scenario_handler] get a error: ~p", [Reason]),
|
||||
{ok, 200, iot_util:json_error(404, <<"database error">>)}
|
||||
end;
|
||||
|
||||
handle_request("GET", "/scenario/detail", #{<<"id">> := ScenarioId0}, _) ->
|
||||
ScenarioId = binary_to_integer(ScenarioId0),
|
||||
case scenario_model:get_scenario(ScenarioId) of
|
||||
undefined ->
|
||||
{ok, 200, iot_util:json_error(404, <<"scenario not found">>)};
|
||||
{ok, Scenario} ->
|
||||
ScenarioInfo = scenario_model:to_map(Scenario),
|
||||
%% 获取场景下部署的主机列表
|
||||
{ok, DeployList0} = scenario_deploy_model:get_scenario_deploy_list(ScenarioId),
|
||||
DeployList = lists:map(fun scenario_deploy_model:to_map/1, DeployList0),
|
||||
ScenarioInfo1 = maps:put(<<"deploy_list">>, ScenarioInfo, DeployList),
|
||||
%% 获取部署的主机信息
|
||||
ScenarioInfo2 = lists:map(fun(Info = #{<<"host_id">> := HostId}) ->
|
||||
case host_model:get_host(HostId) of
|
||||
{ok, Host} ->
|
||||
HostInfo = host_model:to_map(Host),
|
||||
Info#{<<"host_info">> => HostInfo};
|
||||
undefined ->
|
||||
Info#{<<"host_info">> => #{}}
|
||||
end
|
||||
end, ScenarioInfo1),
|
||||
|
||||
{ok, 200, iot_util:json_data(ScenarioInfo2)}
|
||||
end;
|
||||
|
||||
handle_request("POST", "/scenario/change_status", _, #{<<"scenario_id">> := ScenarioId, <<"status">> := Status}) when is_integer(ScenarioId), is_integer(Status) ->
|
||||
case scenario_model:change_status(ScenarioId, Status) of
|
||||
ok ->
|
||||
{ok, 200, iot_util:json_data(<<"success">>)};
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
lager:warning("[http_scenario_handler] change_status get a error: ~p", [Reason]),
|
||||
{ok, 200, iot_util:json_error(404, Reason)}
|
||||
end;
|
||||
|
||||
%% 部署
|
||||
handle_request("POST", "/scenario/deploy", _, #{<<"scenario_id">> := ScenarioId, <<"status">> := Status}) when is_integer(ScenarioId), is_integer(Status) ->
|
||||
case scenario_model:change_status(ScenarioId, Status) of
|
||||
ok ->
|
||||
{ok, 200, iot_util:json_data(<<"success">>)};
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
lager:warning("[http_scenario_handler] change_status get a error: ~p", [Reason]),
|
||||
{ok, 200, iot_util:json_error(404, Reason)}
|
||||
end;
|
||||
|
||||
%% 删除??
|
||||
handle_request("POST", "/scenario/change_status", _, #{<<"scenario_id">> := ScenarioId, <<"status">> := Status}) when is_integer(ScenarioId), is_integer(Status) ->
|
||||
case scenario_model:change_status(ScenarioId, Status) of
|
||||
ok ->
|
||||
{ok, 200, iot_util:json_data(<<"success">>)};
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
lager:warning("[http_scenario_handler] change_status get a error: ~p", [Reason]),
|
||||
{ok, 200, iot_util:json_error(404, Reason)}
|
||||
end.
|
||||
@ -1,211 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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 convert_terminal_info(TerminalInfo) of
|
||||
{ok, Terminal} ->
|
||||
TerminalId = terminal_model:next_id(),
|
||||
case terminal_model:add_terminal(Terminal#terminal{terminal_id = TerminalId}) 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;
|
||||
{error, Error} ->
|
||||
{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]),
|
||||
|
||||
{SuccessList, FailedList} = lists:foldl(fun(TerminalInfo, {SuccessList0, FailedList0}) ->
|
||||
case convert_terminal_info(TerminalInfo) of
|
||||
{ok, Terminal = #terminal{serial_number = SerialNumber}} ->
|
||||
TerminalId = terminal_model:next_id(),
|
||||
case terminal_model:add_terminal(Terminal#terminal{terminal_id = TerminalId}) of
|
||||
ok ->
|
||||
SuccessItem = #{<<"serial_number">> => SerialNumber, <<"terminal_id">> => TerminalId},
|
||||
{[SuccessItem|SuccessList0], FailedList0};
|
||||
{error, Reason} when is_binary(Reason) ->
|
||||
ErrorItem = maps:put(<<"error_message">>, Reason, TerminalInfo),
|
||||
{SuccessList0, [ErrorItem|FailedList0]}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
ErrorItem = maps:put(<<"error_message">>, Reason, TerminalInfo),
|
||||
{SuccessList0, [ErrorItem|FailedList0]}
|
||||
end
|
||||
end, {[], []}, TerminalInfos),
|
||||
ImportResult = #{
|
||||
<<"success_list">> => lists:reverse(SuccessList),
|
||||
<<"failed_list">> => lists:reverse(FailedList)
|
||||
},
|
||||
|
||||
{ok, 200, iot_util:json_data(ImportResult)};
|
||||
|
||||
%% 获取终端的统计信息
|
||||
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(#{<<"host_id">> := HostId, <<"serial_number">> := SerialNumber, <<"name">> := Name, <<"code">> := Code, <<"access_protocol">> := AccessProtocol,
|
||||
<<"product_id">> := ProductId, <<"vendor_id">> := VendorId, <<"model">> := Model, <<"cell_id">> := CellId}) ->
|
||||
|
||||
CheckResult = if
|
||||
Name == <<>> ->
|
||||
{error, <<"name is empty">>};
|
||||
HostId == <<>> ->
|
||||
{error, <<"host_id is empty">>};
|
||||
SerialNumber == <<>> ->
|
||||
{error, <<"serial_number is empty">>};
|
||||
Code == <<>> ->
|
||||
{error, <<"code is empty">>};
|
||||
AccessProtocol == <<>> ->
|
||||
{error, <<"access_protocol is empty">>};
|
||||
Model == <<>> ->
|
||||
{error, <<"model is empty">>};
|
||||
not is_integer(ProductId) orelse ProductId < 0 ->
|
||||
{error, <<"product_id is error">>};
|
||||
not is_integer(VendorId) orelse VendorId < 0 ->
|
||||
{error, <<"vendor_id is error">>};
|
||||
not is_integer(CellId) orelse CellId < 0 ->
|
||||
{error, <<"cell_id is error">>};
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
|
||||
case CheckResult of
|
||||
ok ->
|
||||
{ok, #terminal{
|
||||
%% 关联主机
|
||||
host_id = HostId,
|
||||
%% 序列号
|
||||
serial_number = SerialNumber,
|
||||
%% 名称
|
||||
name = Name,
|
||||
%% 设备编码
|
||||
code = Code,
|
||||
%% 接入协议
|
||||
access_protocol = AccessProtocol,
|
||||
%% 产品ID,枚举类型
|
||||
product_id = ProductId,
|
||||
%% 厂商ID,枚举类型
|
||||
vendor_id = VendorId,
|
||||
%% 型号
|
||||
model = Model,
|
||||
%% 所在单元ID
|
||||
cell_id = CellId,
|
||||
%% 终端状态
|
||||
status = ?TERMINAL_STATUS_OFFLINE,
|
||||
%% 最后上线时间
|
||||
update_ts = iot_util:current_time()
|
||||
}};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
convert_terminal_info(_) ->
|
||||
{error, <<"miss required param">>}.
|
||||
190
apps/iot/src/influxdb/influx_client.erl
Normal file
190
apps/iot/src/influxdb/influx_client.erl
Normal file
@ -0,0 +1,190 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 30. 5月 2023 10:48
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(influx_client).
|
||||
-author("aresei").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/1, write/4, write/5, test/0]).
|
||||
-export([get_precision/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-define(INFLUX_POOl, influx_pool).
|
||||
|
||||
-record(state, {
|
||||
host,
|
||||
port,
|
||||
token :: binary()
|
||||
}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
test() ->
|
||||
UUID = <<"device123123">>,
|
||||
|
||||
lists:foreach(fun(Id) ->
|
||||
Point = influx_point:new(<<"shui_biao">>,
|
||||
[{<<"uuid">>, UUID}, {<<"service_name">>, <<"shui_biao">>}],
|
||||
[{<<"cost">>, Id}],
|
||||
iot_util:timestamp()),
|
||||
|
||||
poolboy:transaction(influx_pool, fun(Pid) ->
|
||||
write(Pid, <<"iot">>, <<"iot">>, [Point])
|
||||
end)
|
||||
end, lists:seq(1, 100)).
|
||||
|
||||
%% 获取时间标识符号
|
||||
-spec get_precision(Timestamp :: integer()) -> binary().
|
||||
get_precision(Timestamp) when is_integer(Timestamp) ->
|
||||
case length(integer_to_list(Timestamp)) of
|
||||
10 ->
|
||||
<<"s">>;
|
||||
13 ->
|
||||
<<"ms">>;
|
||||
16 ->
|
||||
<<"u">>;
|
||||
19 ->
|
||||
<<"ns">>;
|
||||
_ ->
|
||||
<<"ms">>
|
||||
end.
|
||||
|
||||
-spec write(Pid :: pid(), Bucket :: binary(), Org :: binary(), Points :: list()) -> no_return().
|
||||
write(Pid, Bucket, Org, Points) when is_pid(Pid), is_binary(Bucket), is_binary(Org), is_list(Points) ->
|
||||
write(Pid, Bucket, Org, <<"ms">>, Points).
|
||||
|
||||
%% Precision的值为: ms|ns|s; 表示时间的精度,默认为毫秒(ms)
|
||||
-spec write(Pid :: pid(), Bucket :: binary(), Org :: binary(), Precision :: binary(), Points :: list()) -> no_return().
|
||||
write(Pid, Bucket, Org, Precision, Points) when is_pid(Pid), is_binary(Bucket), is_binary(Org), is_binary(Precision), is_list(Points) ->
|
||||
gen_server:cast(Pid, {write, Bucket, Org, Precision, Points}).
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link(Opts :: list()) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(Opts) when is_list(Opts) ->
|
||||
gen_server:start_link(?MODULE, [Opts], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% 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([InfluxProps]) ->
|
||||
Token = proplists:get_value(token, InfluxProps),
|
||||
Host = proplists:get_value(host, InfluxProps),
|
||||
Port = proplists:get_value(port, InfluxProps),
|
||||
|
||||
{ok, #state{host = Host, port = Port, token = Token}}.
|
||||
|
||||
%% @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({write, Bucket, Org, Precision, Points}, State = #state{host = Host, port = Port, token = Token}) ->
|
||||
%% 处理headers
|
||||
Headers = [
|
||||
{<<"Accept">>, <<"application/json">>},
|
||||
{<<"Accept-Encoding">>, <<"identity">>},
|
||||
{<<"Content-Type">>, <<"text/plain; charset=utf-8">>},
|
||||
{<<"Content-Encoding">>, <<"gzip">>},
|
||||
{<<"Authorization">>, <<"Token ", Token/binary>>}
|
||||
],
|
||||
|
||||
%% 处理points
|
||||
PointLines = lists:map(fun influx_point:normalized/1, Points),
|
||||
Body = iolist_to_binary(lists:join(<<"\n">>, PointLines)),
|
||||
%% gzip压缩
|
||||
GZipBody = zlib:gzip(Body),
|
||||
Query = uri_string:compose_query([{<<"bucket">>, Bucket}, {<<"org">>, Org}, {<<"precision">>, Precision}]),
|
||||
|
||||
Url = uri_string:normalize(#{
|
||||
scheme => "http",
|
||||
host => Host,
|
||||
port => Port,
|
||||
path => "/api/v2/write",
|
||||
query => Query
|
||||
}),
|
||||
|
||||
lager:debug("[influx_client] url is: ~p, headers: ~p, body: ~p", [Url, Headers, Body]),
|
||||
case hackney:request(post, Url, Headers, GZipBody, [{pool, influx_pool}]) of
|
||||
{ok, StatusCode, _RespHeaders, ClientRef} ->
|
||||
case StatusCode >= 200 andalso StatusCode < 300 of
|
||||
true ->
|
||||
case hackney:body(ClientRef) of
|
||||
{ok, RespBody} ->
|
||||
lager:debug("[influx_client] status_code: ~p, response body is: ~p", [StatusCode, RespBody]);
|
||||
{error, Error} ->
|
||||
lager:warning("[influx_client] response error is: ~p", [Error])
|
||||
end;
|
||||
false ->
|
||||
lager:debug("[influx_client] status_code: ~p, response body is: ~p", [StatusCode]),
|
||||
hackney:close(ClientRef)
|
||||
end;
|
||||
{error, Reason} ->
|
||||
lager:warning("[influx_client] request result is: ~p", [Reason])
|
||||
end,
|
||||
|
||||
{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
|
||||
%%%===================================================================
|
||||
54
apps/iot/src/influxdb/influx_point.erl
Normal file
54
apps/iot/src/influxdb/influx_point.erl
Normal file
@ -0,0 +1,54 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 30. 5月 2023 11:28
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(influx_point).
|
||||
-author("aresei").
|
||||
|
||||
-record(point, {
|
||||
measurement,
|
||||
tags = [],
|
||||
fields = [],
|
||||
time = 0 :: integer()
|
||||
}).
|
||||
|
||||
%% API
|
||||
-export([new/4, normalized/1]).
|
||||
|
||||
new(Measurement, Tags, Fields, Timestamp) when is_binary(Measurement), is_list(Tags); is_map(Tags), is_list(Fields); is_map(Fields), is_integer(Timestamp) ->
|
||||
#point{measurement = Measurement, tags = as_list(Tags), fields = as_list(Fields), time = Timestamp}.
|
||||
|
||||
normalized(#point{measurement = Name, tags = Tags, fields = Fields, time = Time}) ->
|
||||
NTags = lists:map(fun({N, V}) -> <<N/binary, $=, V/binary>> end, Tags),
|
||||
NFields = lists:map(fun({K, V}) -> <<K/binary, $=, (field_val(V))/binary>> end, Fields),
|
||||
|
||||
TagItems = lists:join(<<",">>, [Name | NTags]),
|
||||
FieldItems = lists:join(<<",">>, NFields),
|
||||
|
||||
NTime = case Time > 0 of
|
||||
true -> Time;
|
||||
false -> iot_util:timestamp()
|
||||
end,
|
||||
|
||||
erlang:iolist_to_binary([TagItems, <<" ">>, FieldItems, <<" ">>, integer_to_binary(NTime)]).
|
||||
|
||||
field_val(V) when is_integer(V) ->
|
||||
<<(integer_to_binary(V))/binary, "i">>;
|
||||
field_val(V) when is_number(V) ->
|
||||
<<(integer_to_binary(V))/binary, "u">>;
|
||||
field_val(V) when is_binary(V) ->
|
||||
<<$", V/binary, $">>;
|
||||
field_val(true) ->
|
||||
<<"true">>;
|
||||
field_val(false) ->
|
||||
<<"false">>.
|
||||
|
||||
as_list(L) when is_list(L) ->
|
||||
L;
|
||||
as_list(L) when is_map(L) ->
|
||||
maps:to_list(L).
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
parse_trans,
|
||||
hackney,
|
||||
poolboy,
|
||||
mysql,
|
||||
emqtt,
|
||||
mnesia,
|
||||
crypto,
|
||||
|
||||
@ -13,17 +13,16 @@
|
||||
start(_StartType, _StartArgs) ->
|
||||
io:setopts([{encoding, unicode}]),
|
||||
%% 启动数据库
|
||||
mnesia:start(),
|
||||
Tables = mnesia:system_info(tables),
|
||||
%% 加载必须等待的数据库表
|
||||
lists:member(router, Tables) andalso mnesia:wait_for_tables([router], infinity),
|
||||
lists:member(host, Tables) andalso mnesia:wait_for_tables([host], infinity),
|
||||
% start_mnesia(),
|
||||
|
||||
%% 加速内存的回收
|
||||
erlang:system_flag(fullsweep_after, 16),
|
||||
%% 启动http服务
|
||||
start_http_server(),
|
||||
|
||||
%% 启动连接池
|
||||
ok = hackney_pool:start_pool(influx_pool, [{timeout, 150000}, {max_connections, 100}]),
|
||||
|
||||
iot_sup:start_link().
|
||||
|
||||
stop(_State) ->
|
||||
@ -41,19 +40,24 @@ start_http_server() ->
|
||||
|
||||
Dispatcher = cowboy_router:compile([
|
||||
{'_', [
|
||||
{"/host/[...]", http_protocol, [http_host_handler]},
|
||||
{"/terminal/[...]", http_protocol, [http_terminal_handler]},
|
||||
{"/scenario/[...]", http_protocol, [http_scenario_handler]},
|
||||
{"/log/[...]", http_protocol, [http_log_handler]},
|
||||
{"/iot/[...]", http_protocol, [http_iot_handler]},
|
||||
{"/router/[...]", http_protocol, [http_router_handler]}
|
||||
{"/host/[...]", http_protocol, [http_host_handler]}
|
||||
]}
|
||||
]),
|
||||
|
||||
TransOpts = [
|
||||
{port, Port},
|
||||
{num_acceptors, Acceptors},
|
||||
{backlog, Backlog},
|
||||
{max_connections, MaxConnections}
|
||||
],
|
||||
{ok, _} = cowboy:start_clear(http_listener, TransOpts, #{env => #{dispatch => Dispatcher}}),
|
||||
lager:debug("[booking_app] the http server start at: ~p", [Port]).
|
||||
|
||||
{ok, Pid} = cowboy:start_clear(http_listener, TransOpts, #{env => #{dispatch => Dispatcher}}),
|
||||
lager:debug("[iot_app] the http server start at: ~p, pid is: ~p", [Port, Pid]).
|
||||
|
||||
%start_mnesia() ->
|
||||
% %% 启动数据库
|
||||
% mnesia:start(),
|
||||
% Tables = mnesia:system_info(tables),
|
||||
% %% 加载必须等待的数据库表
|
||||
% lists:member(router, Tables) andalso mnesia:wait_for_tables([router], infinity),
|
||||
% lists:member(host, Tables) andalso mnesia:wait_for_tables([host], infinity).
|
||||
@ -10,14 +10,24 @@
|
||||
-author("aresei").
|
||||
|
||||
%% API
|
||||
-export([encrypt/3, decrypt/3]).
|
||||
-export([encrypt/2, decrypt/2]).
|
||||
-export([test/0]).
|
||||
|
||||
%% 基于aes的加密算法
|
||||
-spec encrypt(binary(), binary(), binary()) -> binary().
|
||||
encrypt(Key, IVec, PlainText) when is_binary(Key), is_binary(IVec), is_binary(PlainText) ->
|
||||
crypto:crypto_one_time(aes_128_cfb128, Key, IVec, PlainText, true).
|
||||
test() ->
|
||||
Aes = list_to_binary(iot_util:rand_bytes(32)),
|
||||
Enc = encrypt(Aes, <<"sdfsff hesdfs sfsdfsdffffffffffxyz yes call me">>),
|
||||
Data = decrypt(Aes, Enc),
|
||||
|
||||
lager:debug("enc: ~p, size: ~p, data len is: ~p, data: ~p", [Enc, byte_size(Enc), byte_size(Data), Data]).
|
||||
|
||||
%% 基于aes的加密算法, aes_256_cbc
|
||||
-spec encrypt(binary(), binary()) -> binary().
|
||||
encrypt(Key, PlainText) when is_binary(Key), is_binary(PlainText), byte_size(PlainText) >= 32 ->
|
||||
IV = binary:part(Key, {0, 16}),
|
||||
crypto:crypto_one_time(aes_256_cbc, Key, IV, PlainText, [{encrypt, true}, {padding, pkcs_padding}]).
|
||||
|
||||
%% 基于aes的解密算法
|
||||
-spec decrypt(binary(), binary(), binary()) -> binary().
|
||||
decrypt(Key, IVec, CipherText) when is_binary(Key), is_binary(IVec), is_binary(CipherText) ->
|
||||
crypto:crypto_one_time(aes_128_cfb128, Key, IVec, CipherText, false).
|
||||
-spec decrypt(binary(), binary()) -> binary().
|
||||
decrypt(Key, CipherText) when is_binary(Key), is_binary(CipherText) ->
|
||||
IV = binary:part(Key, {0, 16}),
|
||||
crypto:crypto_one_time(aes_256_cbc, Key, IV, CipherText, [{encrypt, false}, {padding, pkcs_padding}]).
|
||||
@ -10,17 +10,27 @@
|
||||
-author("aresei").
|
||||
|
||||
%% API
|
||||
-export([decode/1, encode/2]).
|
||||
-export([encode/2, decode/2]).
|
||||
|
||||
%% 解密数据
|
||||
decode(EncBin) when is_binary(EncBin) ->
|
||||
%PrivateKey = private_key(),
|
||||
%public_key:decrypt_private(EncBin, PrivateKey).
|
||||
ok.
|
||||
decode(Data, PrivateKey) when is_binary(Data), is_binary(PrivateKey) ->
|
||||
[Pri] = public_key:pem_decode(PrivateKey),
|
||||
PriKeyEntry = public_key:pem_entry_decode(Pri),
|
||||
public_key:decrypt_private(Data, PriKeyEntry).
|
||||
|
||||
%% 解密数据
|
||||
encode(Data, PublicKey) when is_map(Data), is_binary(PublicKey) ->
|
||||
BinData = jiffy:encode(Data, [force_utf8]),
|
||||
encode(BinData, PublicKey);
|
||||
|
||||
encode(Data, PublicKey) when is_binary(Data), is_binary(PublicKey) ->
|
||||
PubBin = <<"-----BEGIN PUBLIC KEY-----\n", PublicKey/binary, "-----END PUBLIC KEY-----">>,
|
||||
PubBin = format_public_key(PublicKey),
|
||||
[Pub] = public_key:pem_decode(PubBin),
|
||||
PubKey = public_key:pem_entry_decode(Pub),
|
||||
public_key:encrypt_public(Data, PubKey).
|
||||
public_key:encrypt_public(Data, PubKey).
|
||||
|
||||
%% 格式化pubic_key
|
||||
format_public_key(PubKey = <<"-----BEGIN PUBLIC KEY-----\n", _/binary>>) ->
|
||||
PubKey;
|
||||
format_public_key(PubKey) ->
|
||||
<<"-----BEGIN PUBLIC KEY-----\n", PubKey/binary, "-----END PUBLIC KEY-----">>.
|
||||
@ -1,13 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%% 用来处理和封装通过mqtt协议下发的命令
|
||||
%%% @end
|
||||
%%% Created : 17. 4月 2023 19:20
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_commander).
|
||||
-author("licheng5").
|
||||
|
||||
%% API
|
||||
-export([]).
|
||||
@ -10,9 +10,9 @@
|
||||
-author("licheng5").
|
||||
|
||||
%% API
|
||||
-export([emqt_opts/0]).
|
||||
-export([emqt_opts/1]).
|
||||
|
||||
emqt_opts() ->
|
||||
emqt_opts(ClientSuffix) when is_binary(ClientSuffix) ->
|
||||
%% 建立到emqx服务器的连接
|
||||
{ok, Props} = application:get_env(iot, emqx_server),
|
||||
EMQXHost = proplists:get_value(host, Props),
|
||||
@ -21,7 +21,11 @@ emqt_opts() ->
|
||||
Password = proplists:get_value(password, Props),
|
||||
RetryInterval = proplists:get_value(retry_interval, Props, 5),
|
||||
Keepalive = proplists:get_value(keepalive, Props, 86400),
|
||||
|
||||
Node = atom_to_binary(node()),
|
||||
ClientId = <<"mqtt-client-", Node/binary, "-", ClientSuffix/binary>>,
|
||||
[
|
||||
{clientid, ClientId},
|
||||
{host, EMQXHost},
|
||||
{port, EMQXPort},
|
||||
{owner, self()},
|
||||
@ -32,4 +36,4 @@ emqt_opts() ->
|
||||
{auto_ack, true},
|
||||
{proto_ver, v5},
|
||||
{retry_interval, RetryInterval}
|
||||
].
|
||||
].
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% TODO
|
||||
%%% 1. 指令下发是需要微服务名称的,微服务的名称里面是带copy的完整名称: (modbus:12345)
|
||||
%%% @end
|
||||
%%% Created : 12. 3月 2023 21:27
|
||||
%%%-------------------------------------------------------------------
|
||||
@ -12,9 +13,12 @@
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% 心跳包的间隔周期, 值需要比host上传的间隔大一些才行
|
||||
-define(TICKER_INTERVAL, 5000 * 2).
|
||||
|
||||
%% API
|
||||
-export([test/1]).
|
||||
-export([start_link/2, get_name/1, get_pid/1, publish/2, publish_result/2]).
|
||||
-export([start_link/2, get_name/1, get_pid/1, handle/2, reload/1, activate/1]).
|
||||
-export([get_metric/1, build_command/3, downstream_topic/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
@ -22,47 +26,76 @@
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-record(state, {
|
||||
host :: #host{},
|
||||
emqx_pid :: pid(),
|
||||
%% 发送的未确认的包
|
||||
inflight = #{}
|
||||
}).
|
||||
%% 从数据库里面读取到的数据
|
||||
uuid :: binary(),
|
||||
%% 当前的状态
|
||||
status :: integer(),
|
||||
|
||||
test(Msg) when is_binary(Msg) ->
|
||||
Pid = get_pid(<<"1">>),
|
||||
publish(Pid, Msg).
|
||||
%% rsa公钥
|
||||
pub_key = <<>> :: binary(),
|
||||
%% aes的key, 后续通讯需要基于这个加密
|
||||
aes = <<>> :: binary(),
|
||||
|
||||
%% 主机的相关信息
|
||||
metrics = #{} :: map(),
|
||||
|
||||
%% 是否获取到了ping请求
|
||||
is_answered = false :: boolean(),
|
||||
|
||||
%% 标识当前主机是否已经注册,每次会话时主机需要重新协商会话; 通过status的值判断是否已经激活,值为:-1时,表示未激活
|
||||
is_activated :: boolean(),
|
||||
|
||||
%% 会话状态
|
||||
has_session = false :: boolean()
|
||||
}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
get_pid(HostId) when is_binary(HostId) ->
|
||||
Name = get_name(HostId),
|
||||
global:whereis_name(Name).
|
||||
|
||||
get_name(HostId) when is_binary(HostId) ->
|
||||
binary_to_atom(<<"iot_host:", HostId/binary>>).
|
||||
-spec get_pid(UUID :: binary()) -> undefined | pid().
|
||||
get_pid(UUID) when is_binary(UUID) ->
|
||||
Name = get_name(UUID),
|
||||
whereis(Name).
|
||||
|
||||
-spec publish(pid(), binary()) -> {ok, Ref :: reference()} | {error, term()}.
|
||||
publish(Pid, Message) when is_pid(Pid), is_binary(Message) ->
|
||||
gen_server:call(Pid, {publish, self(), Message}).
|
||||
-spec get_name(UUID :: binary()) -> atom().
|
||||
get_name(UUID) when is_binary(UUID) ->
|
||||
binary_to_atom(<<"iot_host:", UUID/binary>>).
|
||||
|
||||
%% 获取publish的结果
|
||||
-spec publish_result(tuple(), Timeout :: integer()) -> {ok, PacketId :: integer()} | {error, Reason :: any()}.
|
||||
publish_result({error, Reason}, _) ->
|
||||
{error, Reason};
|
||||
publish_result({ok, Ref}, Timeout) when is_reference(Ref), is_integer(Timeout) ->
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
{ok, PacketId}
|
||||
after Timeout ->
|
||||
{error, timeout}
|
||||
end.
|
||||
%% 获取主机的下行主题
|
||||
-spec downstream_topic(UUID :: binary()) -> Topic :: binary().
|
||||
downstream_topic(UUID) when is_binary(UUID) ->
|
||||
<<"host/downstream/", UUID/binary>>.
|
||||
|
||||
%% 处理消息
|
||||
-spec handle(Pid :: pid(), Payload :: binary() | map()) -> no_return().
|
||||
handle(Pid, Payload) when is_pid(Pid), is_binary(Payload); is_map(Payload) ->
|
||||
gen_server:cast(Pid, {handle, Payload}).
|
||||
|
||||
%% 重新加载主机的基本信息
|
||||
-spec reload(Pid :: pid()) -> ok | {error, Reason :: any()}.
|
||||
reload(Pid) when is_pid(Pid) ->
|
||||
gen_server:call(Pid, reload).
|
||||
|
||||
%% 激活主机
|
||||
-spec activate(Pid :: pid()) -> ok.
|
||||
activate(Pid) when is_pid(Pid) ->
|
||||
gen_server:call(Pid, activate).
|
||||
|
||||
-spec get_metric(Pid :: pid()) -> {ok, MetricInfo :: map()}.
|
||||
get_metric(Pid) when is_pid(Pid) ->
|
||||
gen_server:call(Pid, get_metric).
|
||||
|
||||
-spec build_command(Pid :: pid(), CommandType :: integer(), Params :: binary()) ->
|
||||
{ok, Topic :: binary(), Command :: binary()} | {error, Reason :: any()}.
|
||||
build_command(Pid, CommandType, Params) when is_pid(Pid), is_integer(CommandType), is_binary(Params) ->
|
||||
gen_server:call(Pid, {build_command, CommandType, Params}).
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link(Name :: atom(), Host :: #host{}) ->
|
||||
-spec(start_link(Name :: atom(), UUID :: binary()) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(Name, Host = #host{}) ->
|
||||
gen_server:start_link({global, Name}, ?MODULE, [Host], []).
|
||||
start_link(Name, UUID) when is_atom(Name), is_binary(UUID) ->
|
||||
gen_server:start_link({local, Name}, ?MODULE, [UUID], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
@ -73,23 +106,16 @@ start_link(Name, Host = #host{}) ->
|
||||
-spec(init(Args :: term()) ->
|
||||
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term()} | ignore).
|
||||
init([Host = #host{host_id = HostId}]) ->
|
||||
lager:debug("[iot_host] host_id: ~p, host is: ~p", [HostId, Host]),
|
||||
init([UUID]) ->
|
||||
case host_bo:get_host_by_uuid(UUID) of
|
||||
{ok, #{<<"status">> := Status}} ->
|
||||
%% 启动心跳定时器
|
||||
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
|
||||
|
||||
Opts = iot_config:emqt_opts(),
|
||||
case emqtt:start_link(Opts) of
|
||||
{ok, ConnPid} ->
|
||||
lager:debug("[iot_host] connect success, pid: ~p", [ConnPid]),
|
||||
%% 快速启动避免阻塞iot_host_sup的启动
|
||||
erlang:start_timer(0, self(), subscribe_ticker),
|
||||
|
||||
{ok, #state{host = Host, emqx_pid = ConnPid}};
|
||||
ignore ->
|
||||
lager:debug("[iot_host] connect emqx get ignore"),
|
||||
{stop, ignore};
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_host] connect emqx get error: ~p", [Reason]),
|
||||
{stop, Reason}
|
||||
{ok, #state{uuid = UUID, is_activated = (Status /= ?HOST_STATUS_INACTIVE), status = Status, has_session = false}};
|
||||
undefined ->
|
||||
lager:warning("[iot_host] host uuid: ~p, loaded from mysql failed", [UUID]),
|
||||
ignore
|
||||
end.
|
||||
|
||||
%% @private
|
||||
@ -102,15 +128,39 @@ init([Host = #host{host_id = HostId}]) ->
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_call({publish, ReceiverPid, Message}, _From, State = #state{emqx_pid = ConnPid, inflight = InFlight, host = #host{host_id = HostId}}) ->
|
||||
Topic = <<"/host/", HostId/binary, "/downstream">>,
|
||||
case emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, 2}, {retain, true}]) of
|
||||
{ok, PacketId} ->
|
||||
Ref = make_ref(),
|
||||
{reply, {ok, Ref}, State#state{inflight = maps:put(PacketId, {ReceiverPid, Ref, Message}, InFlight)}};
|
||||
{error, Reason} ->
|
||||
{reply, {error, Reason}, State}
|
||||
end.
|
||||
handle_call(get_metric, _From, State = #state{metrics = Metrics}) ->
|
||||
{reply, {ok, Metrics}, State};
|
||||
|
||||
%% 重新加载主机信息
|
||||
handle_call(reload, _From, State = #state{uuid = UUID}) ->
|
||||
%% 重新加载主机信息
|
||||
case host_bo:get_host_by_uuid(UUID) of
|
||||
{ok, Host = #{<<"status">> := Status}} ->
|
||||
lager:debug("[iot_host] reload host uuid: ~p, successed", [Host]),
|
||||
{reply, ok, State#state{is_activated = (Status /= ?HOST_STATUS_INACTIVE), status = Status}};
|
||||
undefined ->
|
||||
lager:debug("[iot_host] reload host uuid: ~p, failed", [UUID]),
|
||||
{reply, {error, <<"host not found">>}, State}
|
||||
end;
|
||||
|
||||
% 处理主机的激活
|
||||
handle_call(activate, _From, State = #state{uuid = UUID, is_activated = IsActivated}) ->
|
||||
lager:debug("[iot_host] host uuid: ~p, activated, before status is: ~p", [UUID, IsActivated]),
|
||||
{reply, ok, State#state{is_activated = true}};
|
||||
|
||||
%% 创建命令
|
||||
handle_call({build_command, _, _}, _From, State = #state{has_session = false}) ->
|
||||
{reply, {error, <<"会话未建立,发送命令失败"/utf8>>}, State};
|
||||
handle_call({build_command, CommandType, Command}, _From, State = #state{aes = AES, uuid = UUID, has_session = true}) ->
|
||||
DownstreamTopic = downstream_topic(UUID),
|
||||
EncCommand = iot_cipher_aes:encrypt(AES, Command),
|
||||
|
||||
{reply, {ok, DownstreamTopic, <<CommandType:8, EncCommand/binary>>}, State};
|
||||
|
||||
handle_call(Info, _From, State = #state{}) ->
|
||||
lager:debug("[iot_host] handle info: ~p", [Info]),
|
||||
|
||||
{reply, ok, State}.
|
||||
|
||||
%% @private
|
||||
%% @doc Handling cast messages
|
||||
@ -118,8 +168,10 @@ handle_call({publish, ReceiverPid, Message}, _From, State = #state{emqx_pid = Co
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_cast(_Request, State = #state{}) ->
|
||||
{noreply, State}.
|
||||
handle_cast({handle, Payload}, State) ->
|
||||
%% 处理消息
|
||||
NState = handle_message(Payload, State),
|
||||
{noreply, NState#state{is_answered = true}}.
|
||||
|
||||
%% @private
|
||||
%% @doc Handling all non call/cast messages
|
||||
@ -127,38 +179,34 @@ handle_cast(_Request, State = #state{}) ->
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
%% 处理topic的订阅事件
|
||||
handle_info({timeout, _, subscribe_ticker}, State = #state{host = #host{host_id = HostId}, emqx_pid = ConnPid}) ->
|
||||
%% 监听和host相关的全部事件
|
||||
{ok, _} = emqtt:connect(ConnPid),
|
||||
Topics = [
|
||||
{<<"/host/", HostId/binary, "/upstream">>, 1}
|
||||
],
|
||||
SubscribeResult = emqtt:subscribe(ConnPid, Topics),
|
||||
lager:debug("[iot_host] host_id: ~p, subscribe result is: ~p", [HostId, SubscribeResult]),
|
||||
%% 超时,并且没有收到任何消息
|
||||
handle_info({timeout, _, ping_ticker}, State = #state{uuid = UUID, is_answered = IsAnswered, status = Status}) ->
|
||||
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
|
||||
%% 需要考虑到主机未激活的情况,主机未激活,返回: keep_status
|
||||
NextStatus = if
|
||||
not IsAnswered andalso Status == ?HOST_STATUS_ONLINE ->
|
||||
{next_status, ?HOST_STATUS_OFFLINE};
|
||||
IsAnswered andalso Status == ?HOST_STATUS_OFFLINE ->
|
||||
{next_status, ?HOST_STATUS_ONLINE};
|
||||
true ->
|
||||
keep_status
|
||||
end,
|
||||
|
||||
{noreply, State#state{emqx_pid = ConnPid}};
|
||||
|
||||
handle_info({disconnect, ReasonCode, Properties}, State = #state{host = #host{host_id = HostId}}) ->
|
||||
|
||||
lager:debug("[iot_host] host: ~p, Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [HostId, ReasonCode, Properties]),
|
||||
{stop, disconnected, State};
|
||||
handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload}}, State = #state{emqx_pid = _ConnPid, host = #host{host_id = HostId}}) ->
|
||||
lager:debug("[iot_host] host: ~p, Recv a publish packet: ~p, payload: ~p", [HostId, Message, Payload]),
|
||||
{noreply, State};
|
||||
handle_info({puback, Packet = #{packet_id := PacketId}}, State = #state{inflight = Inflight, host = #host{host_id = HostId}}) ->
|
||||
case maps:take(PacketId, Inflight) of
|
||||
{{ReceiverPid, Ref, Message}, Inflight1} ->
|
||||
lager:debug("[iot_host] host: ~p, receive puback packet: ~p, assoc message: ~p", [HostId, Packet, Message]),
|
||||
ReceiverPid ! {ok, Ref, PacketId},
|
||||
{noreply, State#state{inflight = Inflight1}};
|
||||
error ->
|
||||
lager:warning("[iot_host] host: ~p, receive unknown puback packet: ~p", [HostId, Packet]),
|
||||
{noreply, State}
|
||||
case NextStatus of
|
||||
keep_status ->
|
||||
{noreply, State};
|
||||
{next_status, NStatus} ->
|
||||
case host_bo:change_status(UUID, NStatus) of
|
||||
{ok, _} ->
|
||||
{noreply, State#state{status = NStatus}};
|
||||
{error, Reason} ->
|
||||
lager:warning("[iot_host] change host status of uuid: ~p, error: ~p", [UUID, Reason]),
|
||||
{noreply, State}
|
||||
end
|
||||
end;
|
||||
|
||||
handle_info(Info, State = #state{host = #host{host_id = HostId}}) ->
|
||||
lager:debug("host_id: ~p, get info: ~p", [HostId, Info]),
|
||||
handle_info(Info, State = #state{uuid = UUID}) ->
|
||||
lager:warning("[iot_host] host uuid: ~p, get unknown info: ~p", [UUID, Info]),
|
||||
{noreply, State}.
|
||||
|
||||
%% @private
|
||||
@ -168,14 +216,10 @@ handle_info(Info, State = #state{host = #host{host_id = HostId}}) ->
|
||||
%% with Reason. The return value is ignored.
|
||||
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
||||
State :: #state{}) -> term()).
|
||||
terminate(Reason, _State = #state{emqx_pid = ConnPid}) when is_pid(ConnPid) ->
|
||||
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
|
||||
ok = emqtt:disconnect(ConnPid),
|
||||
ok = emqtt:stop(ConnPid),
|
||||
lager:debug("[iot_host] terminate with reason: ~p", [Reason]),
|
||||
ok;
|
||||
terminate(Reason, _State) ->
|
||||
terminate(Reason, #state{uuid = UUID}) ->
|
||||
lager:debug("[iot_host] terminate with reason: ~p", [Reason]),
|
||||
ChangeResult = host_bo:change_status(UUID, ?HOST_STATUS_OFFLINE),
|
||||
lager:debug("[iot_host] change host: ~p, status result is: ~p", [UUID, ChangeResult]),
|
||||
ok.
|
||||
|
||||
%% @private
|
||||
@ -188,4 +232,72 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
%%%===================================================================
|
||||
|
||||
%% 基于topic分离了,因此数据里面不需要uuid
|
||||
handle_message(Payload, State) when is_binary(Payload) ->
|
||||
Message = catch jiffy:decode(Payload, [return_maps]),
|
||||
lager:debug("[iot_host] get message: ~p", [Message]),
|
||||
handle_message(Message, State);
|
||||
|
||||
%% create_session操作
|
||||
handle_message(#{<<"method">> := <<"create_session">>, <<"params">> := #{<<"pub_key">> := PubKey}}, State = #state{is_activated = IsActivated, uuid = UUID}) ->
|
||||
lager:debug("[iot_host] host_id uuid: ~p, create_session", [UUID]),
|
||||
|
||||
Aes = list_to_binary(iot_util:rand_bytes(32)),
|
||||
Reply = case IsActivated of
|
||||
true ->
|
||||
#{<<"a">> => true, <<"aes">> => Aes};
|
||||
false ->
|
||||
#{<<"a">> => false, <<"aes">> => <<"">>}
|
||||
end,
|
||||
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
|
||||
|
||||
{ok, Ref} = iot_mqtt_publisher:publish(downstream_topic(UUID), <<10:8, EncReply/binary>>, 1),
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
lager:debug("[iot_host] host_id uuid: ~p, packet_id: ~p, publish register reply success", [UUID, PacketId]),
|
||||
State#state{pub_key = PubKey, aes = Aes, has_session = true}
|
||||
after 10000 ->
|
||||
lager:debug("[iot_host] host_id uuid: ~p, publish register reply get error is: timeout", [UUID]),
|
||||
State
|
||||
end;
|
||||
|
||||
%% 数据上传, TODO 数据格式需要重新约定
|
||||
handle_message(#{<<"method">> := <<"data">>, <<"params">> := Data}, State = #state{has_session = true, aes = AES, uuid = UUID}) ->
|
||||
PlainData = iot_cipher_aes:decrypt(AES, base64:decode(Data)),
|
||||
case catch jiffy:decode(PlainData, [return_maps]) of
|
||||
Infos when is_list(Infos) ->
|
||||
lager:debug("[iot_host] the data is: ~p", [Infos]),
|
||||
insert_metrics(UUID, Infos);
|
||||
_ ->
|
||||
lager:debug("[iot_message_handler] the metric is invalid json")
|
||||
end,
|
||||
State;
|
||||
|
||||
%% 处理服务器的ping
|
||||
handle_message(#{<<"method">> := <<"ping">>, <<"params">> := CipherMetric}, State = #state{has_session = true, uuid = UUID, aes = AES}) ->
|
||||
MetricsInfo = iot_cipher_aes:decrypt(AES, base64:decode(CipherMetric)),
|
||||
Metrics = jiffy:decode(MetricsInfo, [return_maps]),
|
||||
|
||||
lager:debug("[iot_host] host_id uuid: ~p, get ping: ~p", [UUID, Metrics]),
|
||||
State#state{metrics = Metrics};
|
||||
|
||||
handle_message(Message, State = #state{uuid = UUID, has_session = HasSession}) ->
|
||||
lager:warning("[iot_host] host_id uuid: ~p, get a unknown message: ~p, session: ~p", [UUID, Message, HasSession]),
|
||||
State.
|
||||
|
||||
%% 记录数据, TODO 转换点位信息, Fields里面包含了 <<"device_id">> 信息
|
||||
insert_metrics(UUID, Infos) when is_binary(UUID), is_list(Infos) ->
|
||||
[insert_metrics0(UUID, Info) || Info <- Infos].
|
||||
insert_metrics0(UUID, Info = #{<<"service_name">> := ServiceName, <<"fields">> := FieldsList, <<"tags">> := Tags}) when is_binary(ServiceName) ->
|
||||
Timestamp = maps:get(<<"at">>, Info, iot_util:timestamp()),
|
||||
|
||||
NTags = Tags#{<<"uuid">> => UUID, <<"service_name">> => ServiceName},
|
||||
%% TODO 微服务名前缀作为measurement来保存数据
|
||||
[Measurement | _] = binary:split(ServiceName, <<":">>),
|
||||
|
||||
Points = lists:map(fun(Fields) -> influx_point:new(Measurement, NTags, Fields, Timestamp) end, FieldsList),
|
||||
Precision = influx_client:get_precision(Timestamp),
|
||||
|
||||
poolboy:transaction(influx_pool, fun(Pid) -> influx_client:write(Pid, <<"iot">>, <<"iot">>, Precision, Points) end).
|
||||
@ -1,163 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 12. 3月 2023 21:27
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_host_mocker).
|
||||
-author("aresei").
|
||||
-include("iot.hrl").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/1, publish/2]).
|
||||
|
||||
%% 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, {
|
||||
host_id :: binary(),
|
||||
emqx_pid :: pid()
|
||||
}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
-spec publish(pid(), binary()) -> {ok, PacketId :: integer()} | {error, term()}.
|
||||
publish(Pid, Message) when is_pid(Pid), is_binary(Message) ->
|
||||
gen_server:call(Pid, {publish, Message}).
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link(HostId :: binary()) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(HostId) when is_binary(HostId) ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [HostId], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% 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([HostId]) ->
|
||||
%% 建立到emqx服务器的连接
|
||||
Opts = iot_config:emqt_opts(),
|
||||
case emqtt:start_link(Opts) of
|
||||
{ok, ConnPid} ->
|
||||
lager:debug("[iot_host_mocker] connect success, pid: ~p", [ConnPid]),
|
||||
%% 快速启动避免阻塞iot_host_mocker_sup的启动
|
||||
erlang:start_timer(0, self(), subscribe_ticker),
|
||||
|
||||
{ok, #state{host_id = HostId, emqx_pid = ConnPid}};
|
||||
ignore ->
|
||||
lager:debug("[iot_host_mocker] connect emqx get ignore"),
|
||||
{stop, ignore};
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_host_mocker] connect emqx get error: ~p", [Reason]),
|
||||
{stop, Reason}
|
||||
end.
|
||||
|
||||
%% @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({publish, Message}, _From, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
|
||||
Topic = <<"/host/", HostId/binary, "/upstream">>,
|
||||
Result = emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, 1}]),
|
||||
{reply, Result, 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{}}).
|
||||
%% 处理topic的订阅事件
|
||||
handle_info({timeout, _, subscribe_ticker}, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
|
||||
%% 监听和host相关的全部事件
|
||||
{ok, _} = emqtt:connect(ConnPid),
|
||||
Topics = [
|
||||
{<<"/host/", HostId/binary, "/downstream">>, 2}
|
||||
],
|
||||
SubscribeResult = emqtt:subscribe(ConnPid, Topics),
|
||||
lager:debug("[iot_host_mocker] host_id: ~p, subscribe result is: ~p", [HostId, SubscribeResult]),
|
||||
|
||||
{noreply, State#state{emqx_pid = ConnPid}};
|
||||
|
||||
handle_info({disconnect, ReasonCode, Properties}, State = #state{host_id = HostId}) ->
|
||||
|
||||
lager:debug("[iot_host_mocker] host: ~p, Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [HostId, ReasonCode, Properties]),
|
||||
{stop, disconnected, State};
|
||||
%% 收到qos为2的包
|
||||
handle_info({publish, Message = #{packet_id := PacketId, payload := Payload, reason_code := ReasonCode, qos := 2}}, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
|
||||
lager:debug("[iot_host_mocker] host: ~p, qos: 2, Recv a publish packet: ~p, payload: ~p", [HostId, Message, Payload]),
|
||||
%% 回复收到的请求信息
|
||||
% emqtt:pubrec(ConnPid, PacketId, ReasonCode),
|
||||
|
||||
{noreply, State};
|
||||
|
||||
handle_info({publish, Message = #{packet_id := PacketId, payload := Payload}}, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
|
||||
lager:debug("[iot_host_mocker] host: ~p, Recv a publish packet: ~p, payload: ~p, packet_id: ~p", [HostId, Message, Payload, PacketId]),
|
||||
%% 回复收到的请求信息
|
||||
% emqtt:pubrec(ConnPid, PacketId),
|
||||
{noreply, State};
|
||||
handle_info({puback, #{packet_id := PacketId, reason_code := ReasonCode}}, State = #state{host_id = HostId}) ->
|
||||
lager:debug("[iot_host_mocker] host: ~p, receive puback packet_id: ~p, reason_code: ~p", [HostId, PacketId, ReasonCode]),
|
||||
|
||||
{noreply, State};
|
||||
|
||||
handle_info(Info, State = #state{host_id = HostId}) ->
|
||||
lager:debug("host_id: ~p, get info: ~p", [HostId, Info]),
|
||||
{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{emqx_pid = ConnPid}) when is_pid(ConnPid) ->
|
||||
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
|
||||
ok = emqtt:disconnect(ConnPid),
|
||||
ok = emqtt:stop(ConnPid),
|
||||
ok;
|
||||
terminate(Reason, _State) ->
|
||||
lager:debug("[iot_host_mocker] terminate with reason: ~p", [Reason]),
|
||||
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
|
||||
%%%===================================================================
|
||||
@ -9,25 +9,41 @@
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([start_link/0, init/1]).
|
||||
-export([start_link/0, init/1, delete_host/1, ensured_host_started/1]).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
Specs = case host_model:get_all_hosts() of
|
||||
{ok, Hosts} ->
|
||||
lists:map(fun(Host = #host{host_id = HostId}) ->
|
||||
Id = iot_host:get_name(HostId),
|
||||
#{id => Id,
|
||||
start => {iot_host, start_link, [Id, Host]},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => worker,
|
||||
modules => ['iot_host']}
|
||||
end, Hosts);
|
||||
{error, _} ->
|
||||
[]
|
||||
end,
|
||||
Specs = lists:map(fun child_spec/1, host_bo:get_all_hosts()),
|
||||
|
||||
{ok, {#{strategy => one_for_one, intensity => 1000, period => 3600}, Specs}}.
|
||||
{ok, {#{strategy => one_for_one, intensity => 1000, period => 3600}, Specs}}.
|
||||
|
||||
-spec ensured_host_started(UUID :: binary()) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
|
||||
ensured_host_started(UUID) when is_binary(UUID) ->
|
||||
case iot_host:get_pid(UUID) of
|
||||
undefined ->
|
||||
case supervisor:start_child(?MODULE, child_spec(UUID)) of
|
||||
{ok, Pid} when is_pid(Pid) ->
|
||||
{ok, Pid};
|
||||
{error, {'already_started', Pid}} when is_pid(Pid) ->
|
||||
{ok, Pid};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end;
|
||||
Pid when is_pid(Pid) ->
|
||||
{ok, Pid}
|
||||
end.
|
||||
|
||||
delete_host(UUID) when is_binary(UUID) ->
|
||||
Id = iot_host:get_name(UUID),
|
||||
supervisor:terminate_child(?MODULE, Id).
|
||||
|
||||
child_spec(UUID) ->
|
||||
Id = iot_host:get_name(UUID),
|
||||
#{id => Id,
|
||||
start => {iot_host, start_link, [Id, UUID]},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => worker,
|
||||
modules => ['iot_host']}.
|
||||
@ -1,119 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 10. 3月 2023 16:44
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_issue).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/1]).
|
||||
-export([get_pid/1]).
|
||||
|
||||
%% 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, {
|
||||
issue :: #issue{},
|
||||
timer_ref
|
||||
}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
get_pid(IssueId) when is_integer(IssueId) ->
|
||||
whereis(get_name(IssueId)).
|
||||
|
||||
get_name(IssueId) when is_integer(IssueId) ->
|
||||
list_to_atom("iot_issue:" ++ integer_to_list(IssueId)).
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link(Issue :: #issue{}) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(Issue = #issue{issue_id = IssueId}) ->
|
||||
Name = get_name(IssueId),
|
||||
gen_server:start_link({local, Name}, ?MODULE, [Issue], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% 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([Issue = #issue{timeout = Timeout0, hosts = Hosts}]) ->
|
||||
lager:debug("iot_issue started!!: ~p", [Issue]),
|
||||
%% 启动任务定时器, 默认最长为1个小时
|
||||
Timeout = if Timeout0 > 0 -> Timeout0; true -> 3600 end,
|
||||
TimerRef = erlang:start_timer(Timeout * 1000, self(), issue_task_timeout),
|
||||
|
||||
%% TODO 下发数据到主机
|
||||
lists:map(fun(Host) ->
|
||||
iot_emqtt_client:publish(Host, x, 1)
|
||||
end, Hosts),
|
||||
|
||||
{ok, #state{issue = Issue, timer_ref = TimerRef}}.
|
||||
|
||||
%% @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
|
||||
%%%===================================================================
|
||||
@ -1,73 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 10. 3月 2023 16:44
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_issue_sup).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, start_issue/1]).
|
||||
|
||||
%% 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
|
||||
%%%===================================================================
|
||||
|
||||
%% 启动一个任务
|
||||
-spec start_issue(Issue :: #issue{}) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
|
||||
start_issue(Issue = #issue{}) ->
|
||||
case supervisor:start_child(?MODULE, [Issue]) of
|
||||
{ok, Pid} ->
|
||||
{ok, Pid};
|
||||
{error, {'already_started', Pid}} ->
|
||||
{ok, Pid};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
@ -24,77 +24,13 @@ init_database() ->
|
||||
ok = mnesia:start(),
|
||||
|
||||
%% 创建数据库表
|
||||
%% id生成表
|
||||
mnesia:create_table(id_generator, [
|
||||
{attributes, record_info(fields, id_generator)},
|
||||
{record_name, id_generator},
|
||||
{disc_copies, [node()]},
|
||||
{type, ordered_set}
|
||||
]),
|
||||
|
||||
%% 主机表
|
||||
mnesia:create_table(host, [
|
||||
{attributes, record_info(fields, host)},
|
||||
{record_name, host},
|
||||
{disc_copies, [node()]},
|
||||
{type, ordered_set}
|
||||
]),
|
||||
|
||||
%% 主机终端管理
|
||||
mnesia:create_table(terminal, [
|
||||
{attributes, record_info(fields, terminal)},
|
||||
{record_name, terminal},
|
||||
{disc_copies, [node()]},
|
||||
{type, ordered_set}
|
||||
]),
|
||||
|
||||
%% 主机微服务管理
|
||||
mnesia:create_table(service, [
|
||||
{attributes, record_info(fields, service)},
|
||||
{record_name, service},
|
||||
{disc_copies, [node()]},
|
||||
{type, ordered_set}
|
||||
]),
|
||||
|
||||
%% 转发规则表
|
||||
mnesia:create_table(router, [
|
||||
{attributes, record_info(fields, router)},
|
||||
{record_name, router},
|
||||
{disc_copies, [node()]},
|
||||
{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}
|
||||
]),
|
||||
|
||||
%% 操作日志表
|
||||
mnesia:create_table(log, [
|
||||
{attributes, record_info(fields, log)},
|
||||
{record_name, log},
|
||||
{disc_copies, [node()]},
|
||||
{type, ordered_set}
|
||||
]),
|
||||
%mnesia:create_table(host, [
|
||||
% {attributes, record_info(fields, host)},
|
||||
% {record_name, host},
|
||||
% {disc_copies, [node()]},
|
||||
% {type, ordered_set}
|
||||
%]),
|
||||
|
||||
ok.
|
||||
|
||||
@ -114,11 +50,5 @@ copy_database(MasterNode) when is_atom(MasterNode) ->
|
||||
mnesia:change_table_copy_type(schema, node(), disc_copies),
|
||||
|
||||
%% 增加表的分区复制
|
||||
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),
|
||||
mnesia:add_table_copy(log, node(), ram_copies),
|
||||
% mnesia:add_table_copy(host, node(), ram_copies),
|
||||
ok.
|
||||
@ -1,157 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14. 2月 2023 20:32
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_mock).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([insert_hosts/0, insert_services/1, insert_terminals/1, insert_routers/0, insert_logs/0]).
|
||||
-export([start_router/1]).
|
||||
-export([rsa_encode/1]).
|
||||
-export([start_issue/0]).
|
||||
|
||||
start_issue() ->
|
||||
iot_issue_sup:start_issue(#issue{
|
||||
issue_id = 1,
|
||||
name = <<"issue 1">>,
|
||||
uid = 1234,
|
||||
deploy_type = 1,
|
||||
assoc_id = 1,
|
||||
hosts = [<<"host1">>, <<"host2">>],
|
||||
timeout = 6,
|
||||
create_ts = 0,
|
||||
status = 0
|
||||
}).
|
||||
|
||||
insert_hosts() ->
|
||||
lists:foreach(fun(Id0) ->
|
||||
%Id0 = iot_util:rand_bytes(16),
|
||||
Host = #host{
|
||||
host_id = integer_to_binary(Id0),
|
||||
name = <<"N1000_0001">>,
|
||||
model = <<"N1000">>,
|
||||
cell_id = rand:uniform(100),
|
||||
status = 1
|
||||
},
|
||||
|
||||
host_model:add_host(Host)
|
||||
end, lists:seq(1, 1)).
|
||||
|
||||
insert_logs() ->
|
||||
lists:foreach(fun(Id0) ->
|
||||
Log = #log{
|
||||
log_id = Id0,
|
||||
device_type = ?DEVICE_HOST,
|
||||
action_name = <<"主机上线"/utf8>>,
|
||||
assoc_id = <<"1">>,
|
||||
create_ts = Id0 + 123456
|
||||
},
|
||||
log_model:add_log(Log)
|
||||
end, lists:seq(1, 500000)).
|
||||
|
||||
insert_services(HostId) ->
|
||||
lists:foreach(fun(Id0) ->
|
||||
Q0 = queue:new(),
|
||||
Q = queue:in({1234, 21}, Q0),
|
||||
Service = #service{
|
||||
service_id = integer_to_binary(Id0),
|
||||
host_id = HostId,
|
||||
name = <<"Service_0001">>,
|
||||
category = <<"电器"/utf8>>,
|
||||
%% 应用对象
|
||||
apply_object = <<"暂时不清楚"/utf8>>,
|
||||
%% 版本
|
||||
version = <<"v1.1">>,
|
||||
%% 执行次数
|
||||
execute_count = 10,
|
||||
%% 部署时间
|
||||
deploy_ts = 0,
|
||||
metrics = [#service_metric{symbol = <<"X10">>, name = <<"温度"/utf8>>, last_value = 10, unit = <<"E">>, queue = Q}],
|
||||
status = 0
|
||||
},
|
||||
|
||||
service_model:add_service(Service)
|
||||
end, lists:seq(1, 100)).
|
||||
|
||||
insert_terminals(HostId) ->
|
||||
lists:foreach(fun(Id0) ->
|
||||
Terminal = #terminal{
|
||||
terminal_id = integer_to_binary(Id0),
|
||||
host_id = HostId,
|
||||
name = <<"Service_0001">>,
|
||||
code = <<"1234">>,
|
||||
access_protocol = <<"TCP/IP">>,
|
||||
product_id = 12,
|
||||
vendor_id = 13,
|
||||
model = <<"1345">>,
|
||||
cell_id = 12,
|
||||
status = 0
|
||||
},
|
||||
|
||||
terminal_model:add_terminal(Terminal)
|
||||
end, lists:seq(1, 100)).
|
||||
|
||||
insert_routers() ->
|
||||
lists:foreach(fun(Id0) ->
|
||||
R = #router{
|
||||
router_id = Id0,
|
||||
name = <<"计费电表"/utf8>>,
|
||||
rule = <<"测试规则"/utf8>>,
|
||||
endpoint = #http_endpoint{url = <<"http://127.0.0.1:8080/data">>},
|
||||
status = 1
|
||||
},
|
||||
router_model:add_router(R)
|
||||
end, lists:seq(1, 100)).
|
||||
|
||||
start_router(Id0) when is_integer(Id0) ->
|
||||
R = #router{
|
||||
router_id = Id0,
|
||||
name = <<"计费电表"/utf8>>,
|
||||
rule = <<"测试规则"/utf8>>,
|
||||
endpoint = #http_endpoint{url = <<"http://127.0.0.1:8080/data">>},
|
||||
status = 1
|
||||
},
|
||||
router_model:add_router(R),
|
||||
iot_router_sup:start_new_router(R).
|
||||
|
||||
rsa_encode(Data) when is_binary(Data) ->
|
||||
%% 读取相关配置
|
||||
PublicPemFile = "/tmp/keys/public.pem",
|
||||
|
||||
%% 私钥保存解析后的
|
||||
{ok, PubBin} = file:read_file(PublicPemFile),
|
||||
lager:debug("pub bin is: ~p", [PubBin]),
|
||||
[Pub] = public_key:pem_decode(PubBin),
|
||||
lager:debug("pub pem bin is: ~p", [Pub]),
|
||||
PubKey = public_key:pem_entry_decode(Pub),
|
||||
lager:debug("the public key is: ~p", [PubKey]),
|
||||
|
||||
EncData = public_key:encrypt_public(Data, PubKey),
|
||||
lager:debug("enc data is: ~p", [EncData]),
|
||||
|
||||
rsa_decode(EncData),
|
||||
|
||||
ok.
|
||||
|
||||
rsa_decode(EncData) when is_binary(EncData) ->
|
||||
%% 读取相关配置
|
||||
PublicPemFile = "/tmp/keys/pri.pem",
|
||||
|
||||
%% 私钥保存解析后的
|
||||
{ok, PubBin} = file:read_file(PublicPemFile),
|
||||
lager:debug("pub bin is: ~p", [PubBin]),
|
||||
[Pub] = public_key:pem_decode(PubBin),
|
||||
lager:debug("pub pem bin is: ~p", [Pub]),
|
||||
PubKey = public_key:pem_entry_decode(Pub),
|
||||
lager:debug("the public key is: ~p", [PubKey]),
|
||||
|
||||
PlainData = public_key:decrypt_private(EncData, PubKey),
|
||||
lager:debug("plain data is: ~p", [PlainData]),
|
||||
|
||||
ok.
|
||||
@ -1,170 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 18. 2月 2023 21:39
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_mqtt_message_handler).
|
||||
-author("aresei").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([handle/2]).
|
||||
|
||||
handle(<<"server.register">>, Msg = #{<<"c_id">> := ClientId, <<"r">> := PubKey,
|
||||
<<"m">> := #{<<"cpu_core">> := CpuCore, <<"memory">> := Memory, <<"disk">> := Disk, <<"boot_time">> := BootTime, <<"efka_version">> := EfkaVersion, <<"kernel_arch">> := KernelArch, <<"ipv4_1">> := Ip1, <<"ipv4_2">> := Ip2}}) ->
|
||||
lager:debug("[iot_message_handler] get server register message: ~p", [Msg]),
|
||||
|
||||
Aes = iot_util:rand_bytes(16),
|
||||
Host = #host{
|
||||
host_id = ClientId,
|
||||
aes = Aes,
|
||||
metric = #host_metric{
|
||||
%% cpu相关
|
||||
cpus = [
|
||||
#cpu_metric{
|
||||
%% cpu编号
|
||||
num = 1,
|
||||
%% 负载
|
||||
load = 0
|
||||
},
|
||||
#cpu_metric{
|
||||
%% cpu编号
|
||||
num = 2,
|
||||
%% 负载
|
||||
load = 0
|
||||
}
|
||||
],
|
||||
%% cpu温度
|
||||
cpu_temperature = 0,
|
||||
%% 内存状态
|
||||
memory = #memory_metric{
|
||||
%% 使用量
|
||||
used = 0,
|
||||
%% 总量
|
||||
total = Memory
|
||||
},
|
||||
%% 硬盘状态
|
||||
disk = #disk_metric{
|
||||
total = Disk
|
||||
},
|
||||
%% 接口状态
|
||||
interfaces = []
|
||||
},
|
||||
activated_ts = iot_util:current_time(),
|
||||
update_ts = iot_util:current_time(),
|
||||
status = ?HOST_STATUS_INACTIVE
|
||||
},
|
||||
|
||||
case host_model:add_host(Host) of
|
||||
ok ->
|
||||
Reply = #{
|
||||
<<"a">> => true,
|
||||
<<"aes">> => Aes,
|
||||
<<"reply">> => <<"client.reply.", ClientId/binary>>
|
||||
},
|
||||
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
|
||||
lager:debug("enc_reply is: ~p", [EncReply]);
|
||||
{error, Reason} ->
|
||||
lager:debug("register error is: ~p", [Reason]),
|
||||
Reply = #{
|
||||
<<"a">> => false,
|
||||
<<"aes">> => <<"">>,
|
||||
<<"reply">> => <<"client.reply.", ClientId/binary>>
|
||||
},
|
||||
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
|
||||
lager:debug("enc_reply is: ~p", [EncReply])
|
||||
end;
|
||||
|
||||
handle(<<"server.data">>, #{<<"c_id">> := HostId, <<"d">> := Data}) ->
|
||||
case host_model:get_host(HostId) of
|
||||
{ok, #host{aes = Aes}} ->
|
||||
Services = service_model:get_host_services(HostId),
|
||||
PlainData = iot_cipher_aes:decrypt(Aes, Aes, Data),
|
||||
case jiffy:decode(PlainData, [return_maps]) of
|
||||
Infos when is_list(Infos) ->
|
||||
lager:debug("[iot_message_handler] the data is: ~p", [Infos]),
|
||||
%% 一次可能提前多组数据
|
||||
lists:foreach(fun(#{<<"service_name">> := ServiceName, <<"data">> := Items}) ->
|
||||
case lists:search(fun(#service{name = Name}) -> Name =:= ServiceName end, Services) of
|
||||
{value, #service{service_id = ServiceId, metrics = Metrics}} ->
|
||||
%% 更新数据
|
||||
NMetrics = lists:foldl(fun(MetricData, MetricsAcc) -> append_metric(MetricsAcc, MetricData) end, Metrics, Items),
|
||||
case service_model:update_metric(ServiceId, NMetrics) of
|
||||
ok ->
|
||||
lager:debug("[iot_message_handler] update metrics success");
|
||||
{ok, Reason} ->
|
||||
lager:debug("[iot_message_handler] update metrics error: ~p", [Reason])
|
||||
end;
|
||||
false ->
|
||||
lager:warning("[iot_message_handler] host_id: ~p, not found service_name: ~p", [HostId, ServiceName])
|
||||
end
|
||||
end, Infos);
|
||||
_ ->
|
||||
lager:debug("[iot_message_handler] the metric is invalid json")
|
||||
end;
|
||||
undefined ->
|
||||
lager:warning("[iot_message_handler] host_id: ~p, not exists", [HostId])
|
||||
end;
|
||||
|
||||
%% 处理服务器的ping
|
||||
handle(<<"server.ping">>, #{<<"c">> := HostId, <<"at">> := At,
|
||||
<<"h">> := #{<<"fm">> := FreeMemory, <<"fd">> := FreeDisk, <<"cp">> := CpuLoad}}) ->
|
||||
|
||||
case host_model:get_host(HostId) of
|
||||
{ok, Host=#host{metric = Metric = #host_metric{disk = Disk, memory = Memory}}} ->
|
||||
NMetric = Metric#host_metric{
|
||||
disk = Disk#disk_metric{free = FreeDisk},
|
||||
memory = Memory#memory_metric{free = FreeMemory}
|
||||
},
|
||||
|
||||
case mnesia:transaction(fun() -> mnesia:write(host, Host#host{metric = NMetric}, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
lager:warning("[iot_message_handler] host_id: ~p, ping get error: ~p", [HostId, Reason])
|
||||
end;
|
||||
|
||||
undefined ->
|
||||
lager:warning("[iot_message_handler] host_id: ~p, not exists", [HostId])
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
append_metric(Metrics, MetricData) when is_map(MetricData) ->
|
||||
Name = maps:get(<<"name">>, MetricData, <<"">>),
|
||||
Symbol = maps:get(<<"symbol">>, MetricData, <<"">>),
|
||||
Value = maps:get(<<"value">>, MetricData),
|
||||
Ts = maps:get(<<"time">>, MetricData, 0),
|
||||
|
||||
case lists:any(fun(#service_metric{symbol = Symbol0}) -> Symbol =:= Symbol0 end, Metrics) of
|
||||
true ->
|
||||
lists:map(fun(Metric=#service_metric{symbol = Symbol0, queue = Q}) ->
|
||||
case Symbol =:= Symbol0 of
|
||||
true ->
|
||||
Q1 = iot_util:queue_limited_in({Ts, Value}, Q, 100),
|
||||
Metric#service_metric{
|
||||
name = Name,
|
||||
last_value = Value,
|
||||
update_ts = Ts,
|
||||
queue = Q1
|
||||
};
|
||||
false ->
|
||||
Metric
|
||||
end
|
||||
end, Metrics);
|
||||
false ->
|
||||
Q0 = queue:new(),
|
||||
Metric = #service_metric{
|
||||
name = Name,
|
||||
symbol = Symbol,
|
||||
last_value = Value,
|
||||
update_ts = Ts,
|
||||
queue = queue:in({Ts, Value}, Q0)
|
||||
},
|
||||
Metrics ++ [Metric]
|
||||
end.
|
||||
@ -13,7 +13,7 @@
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, publish/3, publish_result/2]).
|
||||
-export([start_link/0, publish/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
@ -33,18 +33,6 @@
|
||||
publish(Topic, Message, Qos) when is_binary(Topic), is_binary(Message), is_integer(Qos) ->
|
||||
gen_server:call(?MODULE, {publish, self(), Topic, Message, Qos}).
|
||||
|
||||
%% 获取publish的结果
|
||||
-spec publish_result(tuple(), Timeout :: integer()) -> {ok, PacketId :: integer()} | {error, Reason :: any()}.
|
||||
publish_result({error, Reason}, _) ->
|
||||
{error, Reason};
|
||||
publish_result({ok, Ref}, Timeout) when is_reference(Ref), is_integer(Timeout) ->
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
{ok, PacketId}
|
||||
after Timeout ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link() ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
@ -62,11 +50,11 @@ start_link() ->
|
||||
{stop, Reason :: term()} | ignore).
|
||||
init([]) ->
|
||||
%% 建立到emqx服务器的连接
|
||||
Opts = iot_config:emqt_opts(),
|
||||
Opts = iot_config:emqt_opts(<<"publisher">>),
|
||||
case emqtt:start_link(Opts) of
|
||||
{ok, ConnPid} ->
|
||||
lager:debug("[iot_mqtt_publisher] connect success, pid: ~p", [ConnPid]),
|
||||
% {ok, _} = emqtt:connect(ConnPid),
|
||||
{ok, _} = emqtt:connect(ConnPid),
|
||||
{ok, #state{conn_pid = ConnPid}};
|
||||
ignore ->
|
||||
lager:debug("[iot_mqtt_publisher] connect emqx get ignore"),
|
||||
@ -87,8 +75,8 @@ init([]) ->
|
||||
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_call({publish, ReceiverPid, Topic, Message, Qos}, _From, State = #state{conn_pid = ConnPid, inflight = InFlight}) ->
|
||||
lager:debug("call me publish"),
|
||||
case emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, Qos}, {retain, true}]) of
|
||||
%% [{qos, Qos}, {retain, true}]
|
||||
case emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, Qos}]) of
|
||||
{ok, PacketId} ->
|
||||
Ref = make_ref(),
|
||||
{reply, {ok, Ref}, State#state{inflight = maps:put(PacketId, {ReceiverPid, Ref, Message}, InFlight)}};
|
||||
@ -119,10 +107,10 @@ handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload}},
|
||||
{noreply, State};
|
||||
handle_info({puback, Packet = #{packet_id := PacketId}}, State = #state{inflight = Inflight}) ->
|
||||
case maps:take(PacketId, Inflight) of
|
||||
{{ReceiverPid, Ref, Message}, Inflight1} ->
|
||||
{{ReceiverPid, Ref, Message}, RestInflight} ->
|
||||
lager:debug("[iot_mqtt_publisher] receive puback packet: ~p, assoc message: ~p", [Packet, Message]),
|
||||
ReceiverPid ! {ok, Ref, PacketId},
|
||||
{noreply, State#state{inflight = Inflight1}};
|
||||
{noreply, State#state{inflight = RestInflight}};
|
||||
error ->
|
||||
lager:warning("[iot_mqtt_publisher] receive unknown puback packet: ~p", [Packet]),
|
||||
{noreply, State}
|
||||
@ -140,7 +128,6 @@ handle_info(Info, State = #state{}) ->
|
||||
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
||||
State :: #state{}) -> term()).
|
||||
terminate(Reason, _State = #state{conn_pid = ConnPid}) when is_pid(ConnPid) ->
|
||||
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
|
||||
ok = emqtt:disconnect(ConnPid),
|
||||
ok = emqtt:stop(ConnPid),
|
||||
lager:debug("[iot_mqtt_publisher] terminate with reason: ~p", [Reason]),
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% 1. 需要考虑集群部署的相关问题,上行的数据可能在集群中共享
|
||||
%%% 2. host进程不能直接去监听topic,这样涉及到新增和下线的很多问题
|
||||
%%% @end
|
||||
%%% Created : 12. 3月 2023 21:27
|
||||
%%%-------------------------------------------------------------------
|
||||
@ -13,47 +14,25 @@
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([test/1]).
|
||||
-export([start_link/0, get_name/1, get_pid/1, publish/2, publish_result/2]).
|
||||
-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).
|
||||
|
||||
%% 需要订阅的主题信息
|
||||
-define(Topics,[
|
||||
{<<"host/upstream/+">>, 1}
|
||||
]).
|
||||
|
||||
-record(state, {
|
||||
conn_pid :: pid()
|
||||
}).
|
||||
|
||||
test(Msg) when is_binary(Msg) ->
|
||||
Pid = get_pid(<<"1">>),
|
||||
publish(Pid, Msg).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
get_pid(HostId) when is_binary(HostId) ->
|
||||
Name = get_name(HostId),
|
||||
global:whereis_name(Name).
|
||||
|
||||
get_name(HostId) when is_binary(HostId) ->
|
||||
binary_to_atom(<<"iot_host:", HostId/binary>>).
|
||||
|
||||
-spec publish(pid(), binary()) -> {ok, Ref :: reference()} | {error, term()}.
|
||||
publish(Pid, Message) when is_pid(Pid), is_binary(Message) ->
|
||||
gen_server:call(Pid, {publish, self(), Message}).
|
||||
|
||||
%% 获取publish的结果
|
||||
-spec publish_result(tuple(), Timeout :: integer()) -> {ok, PacketId :: integer()} | {error, Reason :: any()}.
|
||||
publish_result({error, Reason}, _) ->
|
||||
{error, Reason};
|
||||
publish_result({ok, Ref}, Timeout) when is_reference(Ref), is_integer(Timeout) ->
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
{ok, PacketId}
|
||||
after Timeout ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link() ->
|
||||
@ -72,26 +51,22 @@ start_link() ->
|
||||
{stop, Reason :: term()} | ignore).
|
||||
init([]) ->
|
||||
%% 建立到emqx服务器的连接
|
||||
Opts = iot_config:emqt_opts(),
|
||||
Opts = iot_config:emqt_opts(<<"host-subscriber">>),
|
||||
case emqtt:start_link(Opts) of
|
||||
{ok, ConnPid} ->
|
||||
lager:debug("[iot_mqtt_subscriber] start connecting"),
|
||||
%% 监听和host相关的全部事件
|
||||
%{ok, _} = emqtt:connect(ConnPid),
|
||||
lager:debug("[iot_mqtt_subscriber] connect success, pid: ~p", [ConnPid]),
|
||||
Topics = [
|
||||
{<<"$share/nodes_group//server/register">>, 1},
|
||||
{<<"$share/nodes_group//host/+/upstream">>, 1}
|
||||
],
|
||||
%SubscribeResult = emqtt:subscribe(ConnPid, Topics),
|
||||
% lager:debug("[iot_mqtt_subscriber] subscribe topics: ~p, result is: ~p", [Topics, SubscribeResult]),
|
||||
{ok, _} = emqtt:connect(ConnPid),
|
||||
lager:debug("[iot_mqtt_host_subscriber] connect success, pid: ~p", [ConnPid]),
|
||||
SubscribeResult = emqtt:subscribe(ConnPid, ?Topics),
|
||||
|
||||
lager:debug("[iot_mqtt_host_subscriber] subscribe topics: ~p, result is: ~p", [?Topics, SubscribeResult]),
|
||||
|
||||
{ok, #state{conn_pid = ConnPid}};
|
||||
ignore ->
|
||||
lager:debug("[iot_mqtt_subscriber] connect emqx get ignore"),
|
||||
lager:debug("[iot_mqtt_host_subscriber] connect emqx get ignore"),
|
||||
{stop, ignore};
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_mqtt_subscriber] connect emqx get error: ~p", [Reason]),
|
||||
lager:debug("[iot_mqtt_host_subscriber] connect emqx get error: ~p", [Reason]),
|
||||
{stop, Reason}
|
||||
end.
|
||||
|
||||
@ -124,10 +99,29 @@ handle_cast(_Request, State = #state{}) ->
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_info({disconnect, ReasonCode, Properties}, State = #state{}) ->
|
||||
lager:debug("[iot_mqtt_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
|
||||
lager:debug("[iot_mqtt_host_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
|
||||
{stop, disconnected, State};
|
||||
handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload}}, State = #state{conn_pid = _ConnPid}) ->
|
||||
lager:debug("[iot_mqtt_subscriber] Recv a publish packet: ~p, payload: ~p", [Message, Payload]),
|
||||
%% 必须要做到消息的快速分发,数据的json反序列需要在host进程进行
|
||||
handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload, qos := Qos, topic := Topic}}, State = #state{conn_pid = _ConnPid}) ->
|
||||
lager:debug("[iot_mqtt_subscriber] Recv a publish packet: ~p, qos: ~p", [Message, Qos]),
|
||||
%% 将消息分发到对应的host进程去处理
|
||||
case Topic of
|
||||
<<"host/upstream/", UUID/binary>> ->
|
||||
case iot_host:get_pid(UUID) of
|
||||
HostPid when is_pid(HostPid) ->
|
||||
iot_host:handle(HostPid, Payload);
|
||||
undefined ->
|
||||
%% 尝试加载主机信息,并提交任务
|
||||
case iot_host_sup:ensured_host_started(UUID) of
|
||||
{ok, NewHostPid} ->
|
||||
iot_host:handle(NewHostPid, Payload);
|
||||
{error, Reason} ->
|
||||
lager:warning("[iot_mqtt_subscriber] try start_new_host get error: ~p, assoc packet: ~p", [Reason, Message])
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
lager:warning("[iot_mqtt_subscriber] invalid topic: ~p, packet: ~p, qos: ~p", [Topic, Message, Qos])
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({puback, Packet = #{packet_id := _PacketId}}, State = #state{}) ->
|
||||
lager:debug("[iot_mqtt_subscriber] receive puback packet: ~p", [Packet]),
|
||||
@ -145,9 +139,11 @@ handle_info(Info, State = #state{}) ->
|
||||
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
||||
State :: #state{}) -> term()).
|
||||
terminate(Reason, _State = #state{conn_pid = ConnPid}) when is_pid(ConnPid) ->
|
||||
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
|
||||
%% 取消topic的订阅
|
||||
TopicNames = lists:map(fun({Name, _}) -> Name end, ?Topics),
|
||||
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, TopicNames),
|
||||
|
||||
ok = emqtt:disconnect(ConnPid),
|
||||
ok = emqtt:stop(ConnPid),
|
||||
lager:debug("[iot_mqtt_subscriber] terminate with reason: ~p", [Reason]),
|
||||
ok;
|
||||
terminate(Reason, _State) ->
|
||||
@ -164,4 +160,4 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
%%%===================================================================
|
||||
|
||||
223
apps/iot/src/iot_mqtt_sys_subscriber.erl
Normal file
223
apps/iot/src/iot_mqtt_sys_subscriber.erl
Normal file
@ -0,0 +1,223 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%% 1. 需要考虑集群部署的相关问题,上行的数据可能在集群中共享
|
||||
%%% 2. host进程不能直接去监听topic,这样涉及到新增和下线的很多问题
|
||||
%%% @end
|
||||
%%% Created : 12. 3月 2023 21:27
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_mqtt_sys_subscriber).
|
||||
-author("aresei").
|
||||
-include("iot.hrl").
|
||||
|
||||
-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).
|
||||
|
||||
%% 需要订阅的主题信息
|
||||
-define(Topics,[
|
||||
{<<"system/upstream">>, 1}
|
||||
]).
|
||||
|
||||
-record(state, {
|
||||
conn_pid :: pid(),
|
||||
|
||||
%% 发送完成但是还未收到响应的请求
|
||||
inflight = #{} :: map()
|
||||
}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% 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, ?MODULE}, ?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([]) ->
|
||||
%% 建立到emqx服务器的连接
|
||||
Opts = iot_config:emqt_opts(<<"system-subscriber">>),
|
||||
case emqtt:start_link(Opts) of
|
||||
{ok, ConnPid} ->
|
||||
%% 监听和host相关的全部事件
|
||||
{ok, _} = emqtt:connect(ConnPid),
|
||||
lager:debug("[iot_mqtt_sys_subscriber] connect success, pid: ~p", [ConnPid]),
|
||||
SubscribeResult = emqtt:subscribe(ConnPid, ?Topics),
|
||||
|
||||
lager:debug("[iot_mqtt_sys_subscriber] subscribe topics: ~p, result is: ~p", [?Topics, SubscribeResult]),
|
||||
|
||||
{ok, #state{conn_pid = ConnPid}};
|
||||
ignore ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] connect emqx get ignore"),
|
||||
{stop, ignore};
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] connect emqx get error: ~p", [Reason]),
|
||||
{stop, Reason}
|
||||
end.
|
||||
|
||||
%% @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(_Info, _From, State = #state{conn_pid = _ConnPid}) ->
|
||||
{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({disconnect, ReasonCode, Properties}, State = #state{}) ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
|
||||
{stop, disconnected, State};
|
||||
%% 必须要做到消息的快速分发,数据的json反序列需要在host进程进行
|
||||
handle_info({publish, #{payload := Payload, qos := Qos, topic := <<"system/upstream">>}}, State) ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] Recv a register packet: ~p, qos: ~p", [Payload, Qos]),
|
||||
|
||||
Message = catch jiffy:decode(Payload, [return_maps]),
|
||||
NState = handle_message(Message, State),
|
||||
|
||||
{noreply, NState};
|
||||
|
||||
handle_info({puback, Packet = #{packet_id := _PacketId}}, State = #state{}) ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] receive puback packet: ~p", [Packet]),
|
||||
{noreply, State};
|
||||
|
||||
%% 收到任务的反馈信息
|
||||
handle_info({ok, Ref, _PacketId}, State = #state{inflight = Inflight}) ->
|
||||
case maps:take(Ref, Inflight) of
|
||||
error ->
|
||||
{noreply, State};
|
||||
{{UUID, Msg}, NInflight} ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] send message: ~p, to uuid: ~p, success", [Msg, UUID]),
|
||||
|
||||
{noreply, State#state{inflight = NInflight}}
|
||||
end;
|
||||
|
||||
handle_info(Info, State = #state{}) ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] get info: ~p", [Info]),
|
||||
{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{conn_pid = ConnPid}) when is_pid(ConnPid) ->
|
||||
%% 取消topic的订阅
|
||||
TopicNames = lists:map(fun({Name, _}) -> Name end, ?Topics),
|
||||
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, TopicNames),
|
||||
|
||||
ok = emqtt:disconnect(ConnPid),
|
||||
lager:debug("[iot_mqtt_sys_subscriber] terminate with reason: ~p", [Reason]),
|
||||
ok;
|
||||
terminate(Reason, _State) ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] terminate with reason: ~p", [Reason]),
|
||||
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
|
||||
%%%===================================================================
|
||||
|
||||
%% 处理系统消息
|
||||
handle_message(#{<<"method">> := <<"register">>, <<"params">> := #{<<"uuid">> := UUID}}, State = #state{inflight = Inflight}) when is_binary(UUID) ->
|
||||
Topic = iot_host:downstream_topic(UUID),
|
||||
Qos = 2,
|
||||
%% 查找数据库,如果没有则插入
|
||||
case host_bo:get_host_by_uuid(UUID) of
|
||||
{ok, Host} ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] register, host uuid: ~p, info: ~p, exists", [UUID, Host]),
|
||||
%% 尝试启动主机的服务进程
|
||||
{ok, _} = iot_host_sup:ensured_host_started(UUID),
|
||||
|
||||
Reply = jiffy:encode(#{
|
||||
<<"code">> => 1,
|
||||
<<"message">> => <<"ok">>
|
||||
}),
|
||||
case iot_mqtt_publisher:publish(Topic, <<0:8, Reply/binary>>, Qos) of
|
||||
{ok, Ref} ->
|
||||
State#state{inflight = maps:put(Ref, {UUID, Reply}, Inflight)};
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_host] publish topic get error: ~p", [Reason]),
|
||||
State
|
||||
end;
|
||||
undefined ->
|
||||
case host_bo:create_host(UUID) of
|
||||
{ok, HostId} ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] create host success, uuid: ~p, host_id: ~p", [UUID, HostId]),
|
||||
%% 尝试启动主机的服务进程
|
||||
{ok, _} = iot_host_sup:ensured_host_started(UUID),
|
||||
|
||||
Reply = jiffy:encode(#{
|
||||
<<"code">> => 1,
|
||||
<<"message">> => <<"ok">>
|
||||
}),
|
||||
case iot_mqtt_publisher:publish(Topic, <<0:8, Reply/binary>>, Qos) of
|
||||
{ok, Ref} ->
|
||||
State#state{inflight = maps:put(Ref, {UUID, Reply}, Inflight)};
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_host] publish topic get error: ~p", [Reason]),
|
||||
State
|
||||
end;
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_mqtt_sys_subscriber] create host failed, reason: ~p", [Reason]),
|
||||
Reply = jiffy:encode(#{
|
||||
<<"code">> => 0,
|
||||
<<"message">> => <<"create host failed">>
|
||||
}),
|
||||
case iot_mqtt_publisher:publish(Topic, <<0:8, Reply/binary>>, Qos) of
|
||||
{ok, Ref} ->
|
||||
State#state{inflight = maps:put(Ref, {UUID, Reply}, Inflight)};
|
||||
{error, Reason} ->
|
||||
lager:debug("[iot_host] publish topic get error: ~p", [Reason]),
|
||||
State
|
||||
end
|
||||
end
|
||||
end;
|
||||
handle_message(Msg, State) ->
|
||||
lager:warning("[iot_mqtt_sys_subscriber] get invalid message: ~p", [Msg]),
|
||||
State.
|
||||
@ -1,105 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 01. 3月 2023 16:03
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_router).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/2]).
|
||||
|
||||
-export([get_name/1]).
|
||||
%% 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, {
|
||||
|
||||
}).
|
||||
|
||||
get_name(Id) when is_integer(Id) ->
|
||||
list_to_atom("iot_router:" ++ integer_to_list(Id)).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link(Name :: atom(), Router :: #router{}) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(Name, Router = #router{}) ->
|
||||
gen_server:start_link({global, Name}, ?MODULE, [Router], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% 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([Router]) ->
|
||||
lager:debug("router is: ~p", [Router]),
|
||||
{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
|
||||
%%%===================================================================
|
||||
@ -1,86 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 01. 3月 2023 16:01
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_router_sup).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, start_new_router/1]).
|
||||
|
||||
%% 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 => one_for_one, intensity => 1000, period => 3600},
|
||||
|
||||
%% 启动目前生效的全部转发规则
|
||||
Specs = case router_model:get_all_valid_routers() of
|
||||
{ok, Routers} ->
|
||||
lists:map(fun generate_router_spec/1, Routers);
|
||||
error ->
|
||||
[]
|
||||
end,
|
||||
|
||||
{ok, {SupFlags, Specs}}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
%% 动态启动一个转发规则
|
||||
-spec start_new_router(Router :: #router{}) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
|
||||
start_new_router(Router = #router{}) ->
|
||||
Spec = generate_router_spec(Router),
|
||||
case supervisor:start_child(?MODULE, Spec) of
|
||||
{ok, Pid} ->
|
||||
{ok, Pid};
|
||||
{error, {already_started, Pid}} ->
|
||||
{ok, Pid};
|
||||
{error, Error} ->
|
||||
lager:debug("start router get a error: ~p", [Error]),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
generate_router_spec(Router = #router{router_id = RouterId}) ->
|
||||
Id = iot_router:get_name(RouterId),
|
||||
#{
|
||||
id => Id,
|
||||
start => {'iot_router', start_link, [Id, Router]},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => worker,
|
||||
modules => ['iot_router']
|
||||
}.
|
||||
@ -28,31 +28,13 @@ start_link() ->
|
||||
init([]) ->
|
||||
SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600},
|
||||
ChildSpecs = [
|
||||
%#{
|
||||
% id => 'iot_host_sup',
|
||||
% start => {'iot_host_sup', start_link, []},
|
||||
% restart => permanent,
|
||||
% shutdown => 2000,
|
||||
% type => supervisor,
|
||||
% modules => ['iot_host_sup']
|
||||
%},
|
||||
|
||||
#{
|
||||
id => 'iot_router_sup',
|
||||
start => {'iot_router_sup', start_link, []},
|
||||
id => 'iot_host_sup',
|
||||
start => {'iot_host_sup', start_link, []},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => supervisor,
|
||||
modules => ['iot_router_sup']
|
||||
},
|
||||
|
||||
#{
|
||||
id => 'iot_issue_sup',
|
||||
start => {'iot_issue_sup', start_link, []},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => supervisor,
|
||||
modules => ['iot_issue_sup']
|
||||
modules => ['iot_host_sup']
|
||||
},
|
||||
|
||||
#{
|
||||
@ -63,6 +45,16 @@ init([]) ->
|
||||
type => worker,
|
||||
modules => ['iot_mqtt_subscriber']
|
||||
},
|
||||
|
||||
#{
|
||||
id => 'iot_mqtt_sys_subscriber',
|
||||
start => {'iot_mqtt_sys_subscriber', start_link, []},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => worker,
|
||||
modules => ['iot_mqtt_sys_subscriber']
|
||||
},
|
||||
|
||||
#{
|
||||
id => 'iot_mqtt_publisher',
|
||||
start => {'iot_mqtt_publisher', start_link, []},
|
||||
@ -72,6 +64,12 @@ init([]) ->
|
||||
modules => ['iot_mqtt_publisher']
|
||||
}
|
||||
],
|
||||
{ok, {SupFlags, ChildSpecs}}.
|
||||
{ok, {SupFlags, pools() ++ ChildSpecs}}.
|
||||
|
||||
%% internal functions
|
||||
|
||||
pools() ->
|
||||
{ok, Pools} = application:get_env(iot, pools),
|
||||
lists:map(fun({Name, PoolArgs, WorkerArgs}) ->
|
||||
poolboy:child_spec(Name, [{name, {local, Name}}|PoolArgs], WorkerArgs)
|
||||
end, Pools).
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
-export([timestamp/0, number_format/2, current_time/0]).
|
||||
-export([step/3, chunks/2, rand_bytes/1, uuid/0]).
|
||||
-export([json_data/1, json_error/2]).
|
||||
-export([queue_limited_in/3]).
|
||||
-export([queue_limited_in/3, assert_call/2]).
|
||||
|
||||
%% 时间,精确到毫秒
|
||||
timestamp() ->
|
||||
@ -67,6 +67,7 @@ json_error(ErrCode, ErrMessage) when is_integer(ErrCode), is_binary(ErrMessage)
|
||||
uuid() ->
|
||||
rand_bytes(16).
|
||||
|
||||
-spec rand_bytes(Size :: integer()) -> string().
|
||||
rand_bytes(Size) when is_integer(Size), Size > 0 ->
|
||||
Size1 = erlang:ceil(Size / 2),
|
||||
Bytes = crypto:strong_rand_bytes(Size1),
|
||||
@ -80,4 +81,9 @@ queue_limited_in(Item, Q, Num) when is_integer(Num) ->
|
||||
queue:in(Item, Q1);
|
||||
false ->
|
||||
queue:in(Item, Q)
|
||||
end.
|
||||
end.
|
||||
|
||||
assert_call(true, Fun) ->
|
||||
Fun();
|
||||
assert_call(false, _) ->
|
||||
ok.
|
||||
306
apps/iot/src/mocker/host_mocker.erl
Normal file
306
apps/iot/src/mocker/host_mocker.erl
Normal file
@ -0,0 +1,306 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14. 6月 2023 09:50
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(host_mocker).
|
||||
-author("aresei").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/1, stop/0]).
|
||||
-export([test/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(TICKER_INTERVAL, 5000).
|
||||
|
||||
-record(state, {
|
||||
conn_pid,
|
||||
topic :: binary(),
|
||||
pub_key = <<"-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBMd0+XIwiBAIijwPets3N/MNP
|
||||
zQwm/j6Zlmu5bC+NEDPwmz64OnoLkOTqSnerh7dN2pYeByEkTBu1prtb0cjEf0EP
|
||||
2PP5liUQ6ykEnNfo20vMmSTlKFQZ2qb8jkRltBXTworR4cw92luFuJ0q9VUI3cx2
|
||||
JbOKI9SX52aXGYAc4QIDAQAB
|
||||
-----END PUBLIC KEY-----">>,
|
||||
pri_key = <<"-----BEGIN PRIVATE KEY-----
|
||||
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMEx3T5cjCIEAiKP
|
||||
A962zc38w0/NDCb+PpmWa7lsL40QM/CbPrg6eguQ5OpKd6uHt03alh4HISRMG7Wm
|
||||
u1vRyMR/QQ/Y8/mWJRDrKQSc1+jbS8yZJOUoVBnapvyORGW0FdPCitHhzD3aW4W4
|
||||
nSr1VQjdzHYls4oj1JfnZpcZgBzhAgMBAAECgYEAuf49l6oVpzHgtFCnUyT+4c70
|
||||
YcFwb6HZtpegQjUrWk09E+kB6u8xTC1ElWL37wWLrcJBP8txVraG/sz0F6PR+dxz
|
||||
D3pNaDtZNx1Ey5+LPBtvQRdW3PsUzHYOABX7oXaiB4Wsap9QVOlvjThfTjI+77Fq
|
||||
jwgJr0b2hkrYMgqzUAECQQD2OnlRDmA2Y1kSBwfKyhfQfBBvcHlV/6Qlv/NfXqAo
|
||||
lyl+okF4dK6TMHtuHstz6bf/VZ/7ImPKbCS8VtXuZN3BAkEAyNyaGIp7rbwb4Nee
|
||||
hjeLcC9iB1BpglOFM92v3OGzQtHsUD5J2HrAw3IRKiMzO4unZ5SLF9fTNa/nhZjk
|
||||
cr9HIQJAMXwQ85RTC7stpGzbSQsSfCji2LKfAASPhbKtA6atw1qV0UhkpgO1LgmZ
|
||||
VMlFlDcNbnhT3ZHMwlq3i05cUjvdQQJAXjtwZ7cAUv9/LLq7ekgwuI7iNIA7H5ND
|
||||
WJPWX4/bY6vMa9DtIAxmxsqK1vPwoyzfeq6rmqH8SqGdwoV6F4M5QQJAKZKnhlrx
|
||||
1W9r2qGkn9Bds6CPYUwKZLLXJu32GFe+IXA184DpqBCo9xSxSQ+SN9FuSH5bUKcB
|
||||
c17GntH5fO32Tg==
|
||||
-----END PRIVATE KEY-----">>,
|
||||
|
||||
aes = <<>>
|
||||
}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
test() ->
|
||||
catch stop(),
|
||||
host_mocker:start_link(<<"123123123123123">>).
|
||||
|
||||
stop() ->
|
||||
gen_server:stop(?MODULE).
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link(UUID :: binary()) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(UUID) when is_binary(UUID) ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [UUID], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% 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([UUID]) ->
|
||||
%% 建立到emqx服务器的连接
|
||||
Opts = iot_config:emqt_opts(<<"host-subscriber", UUID/binary>>),
|
||||
{ok, ConnPid} = emqtt:start_link(Opts),
|
||||
%% 监听和host相关的全部事件
|
||||
{ok, _} = emqtt:connect(ConnPid),
|
||||
lager:debug("[iot_mqtt_sys_subscriber] connect success, pid: ~p", [ConnPid]),
|
||||
SubscribeResult = emqtt:subscribe(ConnPid, [
|
||||
{<<"host/downstream/", UUID/binary>>, 1}
|
||||
]),
|
||||
lager:debug("[iot_mqtt_sys_subscriber] subscribe result is: ~p", [SubscribeResult]),
|
||||
|
||||
%% 建立到主机的握手
|
||||
Message = #{
|
||||
<<"method">> => <<"register">>,
|
||||
<<"params">> => #{
|
||||
<<"uuid">> => UUID
|
||||
}
|
||||
},
|
||||
Req = jiffy:encode(Message, [force_utf8]),
|
||||
|
||||
{ok, Ref} = iot_mqtt_publisher:publish(<<"system/upstream">>, Req, 1),
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
lager:debug("[host_mocker] send register success, packet_id: ~p", [PacketId]);
|
||||
{error, Reason} ->
|
||||
lager:debug("[host_mocker] send register failed, reason: ~p", [Reason])
|
||||
end,
|
||||
|
||||
{ok, #state{conn_pid = ConnPid, topic = <<"host/upstream/", UUID/binary>>}}.
|
||||
|
||||
%% @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({disconnect, ReasonCode, Properties}, State = #state{}) ->
|
||||
lager:debug("[host_mocker] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
|
||||
{stop, disconnected, State};
|
||||
%% 必须要做到消息的快速分发,数据的json反序列需要在host进程进行
|
||||
handle_info({publish, #{payload := Payload, qos := Qos, topic := FromTopic}},
|
||||
State = #state{topic = Topic, pub_key = PubKey, pri_key = PrivateKey}) ->
|
||||
|
||||
lager:debug("[host_mocker] Recv a publish packet: ~p, qos: ~p, from topic: ~p", [Payload, Qos, FromTopic]),
|
||||
case Payload of
|
||||
<<0:8, Reply/binary>> ->
|
||||
Json = jiffy:decode(Reply, [return_maps]),
|
||||
lager:debug("[host_mocker] get reply: ~p", [Json]),
|
||||
case Json of
|
||||
#{<<"code">> := 1, <<"message">> := <<"ok">>} ->
|
||||
%% 建立到iot的会话
|
||||
Req = jiffy:encode(#{
|
||||
<<"method">> => <<"create_session">>,
|
||||
<<"params">> => #{
|
||||
<<"pub_key">> => PubKey
|
||||
}
|
||||
}, [force_utf8]),
|
||||
|
||||
{ok, Ref} = iot_mqtt_publisher:publish(Topic, Req, 1),
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
lager:debug("[host_mocker] send create_session success, packet_id: ~p", [PacketId]);
|
||||
{error, Reason} ->
|
||||
lager:debug("[host_mocker] send create_session failed, reason: ~p", [Reason])
|
||||
end;
|
||||
Info ->
|
||||
Info
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
<<Type:8, Command0/binary>> ->
|
||||
Command = iot_cipher_rsa:decode(Command0, PrivateKey),
|
||||
CommandJson = jiffy:decode(Command, [return_maps]),
|
||||
|
||||
lager:debug("[host_mocker] get command: ~p, json: ~p, type: ~p", [Command, CommandJson, Type]),
|
||||
NState = handle_command(Type, CommandJson, State),
|
||||
{noreply, NState}
|
||||
end;
|
||||
|
||||
handle_info({puback, Packet = #{packet_id := _PacketId}}, State = #state{}) ->
|
||||
lager:debug("[iot_mqtt_subscriber] receive puback packet: ~p", [Packet]),
|
||||
{noreply, State};
|
||||
|
||||
%% 周期数据上传逻辑
|
||||
handle_info({timeout, _, data_ticker}, State = #state{aes = Aes, topic = Topic}) ->
|
||||
|
||||
InfoList = [
|
||||
#{
|
||||
<<"service_name">> => <<"shuibiao">>,
|
||||
<<"at">> => iot_util:timestamp(),
|
||||
<<"fields">> => [
|
||||
#{
|
||||
<<"used">> => rand:uniform(2048 * 2)
|
||||
}
|
||||
],
|
||||
<<"tags">> => #{
|
||||
|
||||
}
|
||||
},
|
||||
#{
|
||||
<<"service_name">> => <<"shuibiao:123">>,
|
||||
<<"at">> => iot_util:timestamp(),
|
||||
<<"fields">> => [
|
||||
#{
|
||||
<<"used">> => rand:uniform(2048 * 2)
|
||||
}
|
||||
],
|
||||
<<"tags">> => #{
|
||||
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
Info = jiffy:encode(InfoList),
|
||||
|
||||
Msg = jiffy:encode(#{
|
||||
<<"method">> => <<"data">>,
|
||||
<<"params">> => base64:encode(iot_cipher_aes:encrypt(Aes, Info))
|
||||
}, [force_utf8]),
|
||||
|
||||
{ok, Ref} = iot_mqtt_publisher:publish(Topic, Msg, 1),
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
lager:debug("[host_mocker] send data success, packet_id: ~p", [PacketId]);
|
||||
{error, Reason} ->
|
||||
lager:debug("[host_mocker] send data failed, reason: ~p", [Reason])
|
||||
end,
|
||||
|
||||
erlang:start_timer(?TICKER_INTERVAL + 2000 + rand:uniform(5000), self(), data_ticker),
|
||||
|
||||
{noreply, State};
|
||||
|
||||
%% 周期行的ping逻辑
|
||||
handle_info({timeout, _, ping_ticker}, State = #state{aes = Aes, topic = Topic}) ->
|
||||
Metric = jiffy:encode(#{
|
||||
<<"cpu_load">> => rand:uniform(100),
|
||||
<<"cpu_temperature">> => rand:uniform(100),
|
||||
<<"memory">> => #{
|
||||
<<"used">> => rand:uniform(2048 * 2),
|
||||
<<"total">> => 2048 * 2
|
||||
},
|
||||
<<"disk">> => #{
|
||||
<<"used">> => rand:uniform(2048 * 2),
|
||||
<<"total">> => 2048 * 2
|
||||
},
|
||||
|
||||
<<"interfaces">> => [
|
||||
#{
|
||||
<<"name">> => <<"WiFi无线网络"/utf8>>,
|
||||
<<"detail">> => <<"Managed">>,
|
||||
<<"status">> => 0
|
||||
}
|
||||
]
|
||||
}, [force_utf8]),
|
||||
|
||||
Data = iot_cipher_aes:encrypt(Aes, Metric),
|
||||
|
||||
Msg = jiffy:encode(#{
|
||||
<<"method">> => <<"ping">>,
|
||||
<<"params">> => base64:encode(Data)
|
||||
}, [force_utf8]),
|
||||
|
||||
{ok, Ref} = iot_mqtt_publisher:publish(Topic, Msg, 1),
|
||||
receive
|
||||
{ok, Ref, PacketId} ->
|
||||
lager:debug("[host_mocker] send ping success, packet_id: ~p", [PacketId]);
|
||||
{error, Reason} ->
|
||||
lager:debug("[host_mocker] send ping failed, reason: ~p", [Reason])
|
||||
end,
|
||||
|
||||
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
|
||||
|
||||
{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
|
||||
%%%===================================================================
|
||||
|
||||
handle_command(10, #{<<"aes">> := Aes, <<"a">> := true}, State) ->
|
||||
%% 启动周期ping
|
||||
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
|
||||
|
||||
%% 数据收集
|
||||
erlang:start_timer(?TICKER_INTERVAL + 1000, self(), data_ticker),
|
||||
|
||||
State#state{aes = Aes}.
|
||||
|
||||
65
apps/iot/src/mocker/iot_mock.erl
Normal file
65
apps/iot/src/mocker/iot_mock.erl
Normal file
@ -0,0 +1,65 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 14. 2月 2023 20:32
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(iot_mock).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([rsa_encode/1]).
|
||||
-export([insert_services/1]).
|
||||
|
||||
insert_services(Num) ->
|
||||
lists:foreach(fun(Id) ->
|
||||
Res = mysql_client:insert(<<"micro_service">>,
|
||||
#{
|
||||
<<"name">> => <<"微服务"/utf8, (integer_to_binary(Id))/binary>>,
|
||||
<<"code">> => <<"1223423423423423"/utf8>>,
|
||||
<<"type">> => 1,
|
||||
<<"version">> => <<"v1.0">>,
|
||||
<<"url">> => <<"https://www.baidu.com">>,
|
||||
<<"detail">> => <<"这是一个关于测试的微服务"/utf8>>
|
||||
}, false),
|
||||
lager:debug("insert service result is: ~p", [Res])
|
||||
end, lists:seq(1, Num)).
|
||||
|
||||
rsa_encode(Data) when is_binary(Data) ->
|
||||
%% 读取相关配置
|
||||
PublicPemFile = "/tmp/keys/public.pem",
|
||||
|
||||
%% 私钥保存解析后的
|
||||
{ok, PubBin} = file:read_file(PublicPemFile),
|
||||
lager:debug("pub bin is: ~p", [PubBin]),
|
||||
[Pub] = public_key:pem_decode(PubBin),
|
||||
lager:debug("pub pem bin is: ~p", [Pub]),
|
||||
PubKey = public_key:pem_entry_decode(Pub),
|
||||
lager:debug("the public key is: ~p", [PubKey]),
|
||||
|
||||
EncData = public_key:encrypt_public(Data, PubKey),
|
||||
lager:debug("enc data is: ~p", [EncData]),
|
||||
|
||||
rsa_decode(EncData),
|
||||
|
||||
ok.
|
||||
|
||||
rsa_decode(EncData) when is_binary(EncData) ->
|
||||
%% 读取相关配置
|
||||
PublicPemFile = "/tmp/keys/pri.pem",
|
||||
|
||||
%% 私钥保存解析后的
|
||||
{ok, PubBin} = file:read_file(PublicPemFile),
|
||||
lager:debug("pub bin is: ~p", [PubBin]),
|
||||
[Pub] = public_key:pem_decode(PubBin),
|
||||
lager:debug("pub pem bin is: ~p", [Pub]),
|
||||
PubKey = public_key:pem_entry_decode(Pub),
|
||||
lager:debug("the public key is: ~p", [PubKey]),
|
||||
|
||||
PlainData = public_key:decrypt_private(EncData, PubKey),
|
||||
lager:debug("plain data is: ~p", [PlainData]),
|
||||
|
||||
ok.
|
||||
@ -1,285 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(host_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-define(TAB_NAME, host).
|
||||
|
||||
%% API
|
||||
-export([get_host/1, get_hosts/3, get_all_hosts/0, get_stat/0, add_host/1, change_status/2, delete/1, table_size/0, find_hosts/3, activate/1, update_host/2]).
|
||||
-export([to_map/1, match_spec/1]).
|
||||
|
||||
get_host(HostId) when is_binary(HostId) ->
|
||||
case mnesia:dirty_read(?TAB_NAME, HostId) of
|
||||
[Host = #host{}] ->
|
||||
{ok, Host};
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
get_all_hosts() ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#host.status == ?HOST_STATUS_ONLINE]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Items} when is_list(Items) ->
|
||||
{ok, sort(Items)};
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
%% 获取app信息
|
||||
-spec get_hosts(Spec :: tuple(), Start :: integer(), Limit :: integer()) ->
|
||||
{ok, Items :: list(), TotalNum :: integer()} |
|
||||
{error, Reason :: any()}.
|
||||
get_hosts(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
|
||||
Hosts0 = mnesia:dirty_select(?TAB_NAME, [Spec]),
|
||||
SortedHosts = sort(Hosts0),
|
||||
Len = length(SortedHosts),
|
||||
case Len >= Start + 1 of
|
||||
true ->
|
||||
{ok, lists:sublist(SortedHosts, Start + 1, Limit), Len};
|
||||
false ->
|
||||
{ok, [], Len}
|
||||
end.
|
||||
|
||||
%% 按照状态分组统计
|
||||
get_stat() ->
|
||||
Fun = fun() ->
|
||||
mnesia:foldl(fun(#host{status = Status}, Acc) ->
|
||||
Num = maps:get(Status, Acc, 0),
|
||||
Acc#{Status => Num + 1}
|
||||
end, #{}, ?TAB_NAME)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Stat} when is_map(Stat) ->
|
||||
lists:map(fun({Status, Num}) -> #{<<"status">> => Status, <<"num">> => Num} end, maps:to_list(Stat));
|
||||
{aborted, _} ->
|
||||
#{}
|
||||
end.
|
||||
|
||||
-spec find_hosts(Pred :: fun((#host{}) -> boolean()), Start :: integer(), Limit :: integer()) ->
|
||||
{ok, Items :: [#host{}], Num :: integer()} |
|
||||
{error, Reason :: any()}.
|
||||
find_hosts(Pred, Start, Limit) when is_function(Pred, 1), is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME)]),
|
||||
qlc:fold(fun(Host, Acc) ->
|
||||
case Pred(Host) of
|
||||
true -> [Host|Acc];
|
||||
false -> Acc
|
||||
end
|
||||
end, [], Q)
|
||||
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Hosts0} when is_list(Hosts0) ->
|
||||
Hosts = sort(Hosts0),
|
||||
Len = length(Hosts),
|
||||
case Len >= Start + 1 of
|
||||
true ->
|
||||
{ok, lists:sublist(Hosts, Start + 1, Limit), Len};
|
||||
false ->
|
||||
{ok, [], Len}
|
||||
end;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec add_host(Host :: #host{}) -> ok | {error, Reason :: binary()}.
|
||||
add_host(Host = #host{serial_number = SerialNumber}) ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#host.serial_number =:= SerialNumber]),
|
||||
case qlc:e(Q) of
|
||||
[_SomeHost|_] ->
|
||||
mnesia:abort(<<"serial_number exists">>);
|
||||
[] ->
|
||||
mnesia:write(?TAB_NAME, Host, write)
|
||||
end
|
||||
end,
|
||||
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec change_status(HostId :: binary(), Status :: integer()) -> ok | {error, Reason :: any()}.
|
||||
change_status(HostId, Status) when is_binary(HostId), is_integer(Status) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(?TAB_NAME, HostId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"host not found">>);
|
||||
[Host] ->
|
||||
mnesia:write(?TAB_NAME, Host#host{status = Status}, write)
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec update_host(HostId :: binary(), Fields :: #{}) -> ok | {error, Reason :: any()}.
|
||||
update_host(HostId, Fields) when is_binary(HostId), is_map(Fields) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(?TAB_NAME, HostId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"host not found">>);
|
||||
[Host] ->
|
||||
NHost = lists:foldl(fun(E, Host0) ->
|
||||
case E of
|
||||
{<<"name">>, Name} when is_binary(Name) ->
|
||||
Host0#host{name = Name};
|
||||
{<<"serial_number">>, SerialNumber} when is_binary(SerialNumber) ->
|
||||
Host0#host{serial_number = SerialNumber};
|
||||
{<<"model">>, Model} when is_binary(Model) ->
|
||||
Host0#host{model = Model};
|
||||
{<<"cell_id">>, CellId} when is_integer(CellId) ->
|
||||
Host0#host{cell_id = CellId};
|
||||
{Name, _} ->
|
||||
mnesia:abort(<<"invalid: ", Name/binary>>)
|
||||
end
|
||||
end, Host, maps:to_list(Fields)),
|
||||
|
||||
mnesia:write(?TAB_NAME, NHost, write)
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec activate(HostId :: binary()) -> ok | {error, Reason :: any()}.
|
||||
activate(HostId) when is_binary(HostId) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(host, HostId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"host not found">>);
|
||||
[Host = #host{status = Status}] ->
|
||||
case Status =:= ?HOST_STATUS_INACTIVE of
|
||||
true ->
|
||||
Aes = iot_util:rand_bytes(16),
|
||||
NHost = Host#host{
|
||||
aes = Aes,
|
||||
activated_ts = iot_util:current_time(),
|
||||
update_ts = iot_util:current_time(),
|
||||
status = ?HOST_STATUS_ONLINE
|
||||
},
|
||||
ok = mnesia:write(host, NHost, write),
|
||||
NHost;
|
||||
false ->
|
||||
mnesia:abort(<<"host status invalid">>)
|
||||
end
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Host} ->
|
||||
{ok, Host};
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec delete(HostId :: binary()) -> ok | {error, Reason :: any()}.
|
||||
delete(HostId) when is_binary(HostId) ->
|
||||
case mnesia:transaction(fun() -> mnesia:delete(host, HostId, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取查询的过滤条件
|
||||
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).
|
||||
|
||||
%% 获取app表的数据大小
|
||||
table_size() ->
|
||||
mnesia:table_info(host, size).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%% 主机信息的排序
|
||||
sort(Hosts) when is_list(Hosts) ->
|
||||
lists:sort(fun(#host{name = N1}, #host{name = N2}) -> N1 < N2 end, Hosts).
|
||||
|
||||
%% 将数据转换成hash
|
||||
to_map(#host{host_id = HostId, name = Name, model = Model, cell_id = CellId, activated_ts = ActivatedTs, update_ts = UpdateTs, status = Status, metric = Metric}) ->
|
||||
#{
|
||||
<<"host_id">> => HostId,
|
||||
<<"name">> => Name,
|
||||
<<"model">> => Model,
|
||||
<<"cell_id">> => CellId,
|
||||
<<"activated_ts">> => ActivatedTs,
|
||||
<<"update_ts">> => UpdateTs,
|
||||
<<"status">> => Status,
|
||||
<<"metric">> => host_metric(Metric)
|
||||
}.
|
||||
|
||||
host_metric(undefined) ->
|
||||
#{};
|
||||
host_metric(#host_metric{cpus = Cpus, cpu_temperature = CpuTemperature, memory = Memory, disk = Disk, interfaces = Interfaces}) ->
|
||||
#{
|
||||
<<"cpus">> => host_cpus(Cpus),
|
||||
<<"cpu_temperature">> => CpuTemperature,
|
||||
<<"memory">> => host_memory(Memory),
|
||||
<<"disk">> => host_disk(Disk),
|
||||
<<"interfaces">> => host_interfaces(Interfaces)
|
||||
}.
|
||||
|
||||
host_cpus(Cpus) when is_list(Cpus) ->
|
||||
lists:map(fun(#cpu_metric{num = Num, load = Load}) -> #{<<"num">> => Num, <<"load">> => Load } end, Cpus);
|
||||
host_cpus(_) ->
|
||||
[].
|
||||
|
||||
host_memory(#memory_metric{used = MemoryUsed, total = MemoryTotal}) ->
|
||||
#{<<"used">> => MemoryUsed, <<"total">> => MemoryTotal};
|
||||
host_memory(_) ->
|
||||
#{}.
|
||||
|
||||
host_disk(#disk_metric{used = DiskUsed, total = DiskTotal}) ->
|
||||
#{<<"used">> => DiskUsed, <<"total">> => DiskTotal};
|
||||
host_disk(_) ->
|
||||
#{}.
|
||||
|
||||
%% 转换接口信息
|
||||
host_interfaces(Interfaces) when is_list(Interfaces) ->
|
||||
lists:map(fun(#interface_metric{name = IName, desc = IDesc, status = IStatus, detail = IDetail}) ->
|
||||
#{
|
||||
<<"name">> => IName,
|
||||
<<"desc">> => IDesc,
|
||||
<<"status">> => IStatus,
|
||||
<<"detatil">> => IDetail
|
||||
}
|
||||
end, Interfaces);
|
||||
host_interfaces(_) ->
|
||||
[].
|
||||
@ -1,23 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(id_generator_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
|
||||
%% API
|
||||
-export([generate/1, new_terminal_id/0]).
|
||||
|
||||
%% 生成对应的自增id
|
||||
-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).
|
||||
@ -1,126 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @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 ->
|
||||
Issues0 = mnesia:dirty_select(?TAB_NAME, [Spec]),
|
||||
Issues = sort(Issues0),
|
||||
Len = length(Issues),
|
||||
case Len >= Start + 1 of
|
||||
true ->
|
||||
{ok, lists:sublist(Issues, Start + 1, Limit), Len};
|
||||
false ->
|
||||
{ok, [], Len}
|
||||
end.
|
||||
|
||||
-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, Issues0} when is_list(Issues0) ->
|
||||
Issues = sort(Issues0),
|
||||
Len = length(Issues),
|
||||
case Len >= Start + 1 of
|
||||
true ->
|
||||
{ok, lists:sublist(Issues, Start + 1, Limit), Len};
|
||||
false ->
|
||||
{ok, [], Len}
|
||||
end;
|
||||
{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} ->
|
||||
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
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%% 默认按照id倒序进行排序
|
||||
sort(Issues) when is_list(Issues) ->
|
||||
lists:sort(fun(#issue{issue_id = Id0}, #issue{issue_id = Id1}) -> Id0 < Id1 end, Issues).
|
||||
|
||||
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
|
||||
}.
|
||||
@ -1,126 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(log_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-define(TAB_NAME, log).
|
||||
|
||||
%% API
|
||||
-export([get_logs/1, add_log/1, delete/1, table_size/0, get_last_logs/1]).
|
||||
-export([to_map/1]).
|
||||
|
||||
%% 获取app信息, 该函数的效率较低
|
||||
-spec get_logs(Limit :: integer()) -> {ok, Logs :: list()} | {error, Reason :: any()}.
|
||||
get_logs(Limit) when is_integer(Limit), Limit > 0 ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME)]),
|
||||
%% 按照创建时间倒序排列
|
||||
Order = fun(A, B) -> A#log.create_ts > B#log.create_ts end,
|
||||
Q1 = qlc:sort(Q, [{order, Order}]),
|
||||
|
||||
QC = qlc:cursor(Q1),
|
||||
qlc:next_answers(QC, Limit)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Logs} when is_list(Logs) ->
|
||||
{ok, Logs};
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
%% 获取最新的n条记录
|
||||
-spec get_last_logs(N :: integer()) -> {ok, Logs :: [#log{}]} | {error, Reason :: any()}.
|
||||
get_last_logs(N) when N > 0 ->
|
||||
Fun = fun() ->
|
||||
Keys = read_last_keys(N),
|
||||
lists:flatmap(fun(Key) -> mnesia:read(?TAB_NAME, Key, read) end, lists:reverse(Keys))
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Logs} when is_list(Logs) ->
|
||||
{ok, Logs};
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec add_log(Log :: #log{}) -> ok | {error, Reason :: binary()}.
|
||||
add_log(Log = #log{}) ->
|
||||
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Log, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec delete(LogId :: integer()) -> ok | {error, Reason :: any()}.
|
||||
delete(LogId) when is_binary(LogId) ->
|
||||
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, LogId, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 从后向前变量表
|
||||
read_last_keys(N) when N >= 1 ->
|
||||
case mnesia:last(?TAB_NAME) of
|
||||
'$end_of_table' ->
|
||||
[];
|
||||
LastKey ->
|
||||
read_last_keys0(N - 1, LastKey, [LastKey])
|
||||
end.
|
||||
read_last_keys0(0, _, Keys) ->
|
||||
Keys;
|
||||
read_last_keys0(N, Key, Keys) ->
|
||||
case mnesia:prev(?TAB_NAME, Key) of
|
||||
'$end_of_table' ->
|
||||
Keys;
|
||||
PrevKey ->
|
||||
read_last_keys0(N - 1, PrevKey, [PrevKey|Keys])
|
||||
end.
|
||||
|
||||
%% 获取app表的数据大小
|
||||
table_size() ->
|
||||
mnesia:table_info(?TAB_NAME, size).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%% 主机信息的排序
|
||||
sort(Logs) when is_list(Logs) ->
|
||||
lists:sort(fun(#log{create_ts = Ts0}, #log{create_ts = Ts1}) -> Ts0 > Ts1 end, Logs).
|
||||
|
||||
%% 将数据转换成hash
|
||||
to_map(#log{log_id = LogId, action_name = ActionName, device_type = DeviceType, assoc_id = AssocId, create_ts = CreateTs}) ->
|
||||
DeviceInfo = case DeviceType of
|
||||
?DEVICE_HOST ->
|
||||
case host_model:get_host(AssocId) of
|
||||
{ok, Host} ->
|
||||
host_model:to_map(Host);
|
||||
_ ->
|
||||
#{}
|
||||
end;
|
||||
?DEVICE_TERMINAL ->
|
||||
case terminal_model:get_terminal(AssocId) of
|
||||
{ok, Terminal} ->
|
||||
terminal_model:to_map(Terminal);
|
||||
_ ->
|
||||
#{}
|
||||
end
|
||||
end,
|
||||
|
||||
#{
|
||||
<<"log_id">> => LogId,
|
||||
<<"action_name">> => ActionName,
|
||||
<<"device_type">> => DeviceType,
|
||||
<<"device_info">> => DeviceInfo,
|
||||
<<"create_ts">> => CreateTs
|
||||
}.
|
||||
@ -1,149 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(router_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
%% API
|
||||
-export([get_router/1, get_routers/3, add_router/1, change_status/2, delete/1, table_size/0, get_all_routers/0, get_all_valid_routers/0]).
|
||||
-export([to_map/1, match_spec/1]).
|
||||
|
||||
get_router(RouterId) when is_integer(RouterId) ->
|
||||
case mnesia:dirty_read(router, RouterId) of
|
||||
[Router] ->
|
||||
{ok, Router};
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
%% 获取app信息
|
||||
-spec get_all_routers() -> {ok, Routers :: list()} | {error, Reason :: any()}.
|
||||
get_all_routers() ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(router)]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Routers} ->
|
||||
{ok, sort(Routers)};
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取app信息
|
||||
-spec get_all_valid_routers() -> {ok, Routers :: list()} | {error, Reason :: any()}.
|
||||
get_all_valid_routers() ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(router), E#router.status =:= 1]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Routers} ->
|
||||
{ok, sort(Routers)};
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取app信息
|
||||
-spec get_routers(Filter :: any(), Start :: integer(), Limit :: integer()) ->
|
||||
{ok, Items :: list(), TotalNum :: integer()} |
|
||||
{error, Reason :: any()}.
|
||||
get_routers(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
|
||||
Routers0 = mnesia:dirty_select(router, [Spec]),
|
||||
Routers = sort(Routers0),
|
||||
Len = length(Routers),
|
||||
case Len >= Start + 1 of
|
||||
true ->
|
||||
{ok, lists:sublist(Routers, Start + 1, Limit), Len};
|
||||
false ->
|
||||
{ok, [], Len}
|
||||
end.
|
||||
|
||||
-spec add_router(Router :: #router{}) -> ok | {error, Reason :: any()}.
|
||||
add_router(Router = #router{}) ->
|
||||
case mnesia:transaction(fun() -> mnesia:write(router, Router, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec change_status(RouterId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
|
||||
change_status(RouterId, Status) when is_integer(RouterId), is_integer(Status) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(router, RouterId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"router not found">>);
|
||||
[Router] ->
|
||||
mnesia:write(host, Router#router{status = Status}, write)
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec delete(HostId :: integer()) -> ok | {error, Reason :: any()}.
|
||||
delete(RouterId) when is_integer(RouterId) ->
|
||||
case mnesia:transaction(fun() -> mnesia:delete(router, RouterId, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取查询的过滤条件
|
||||
match_spec(Specs) when is_list(Specs) ->
|
||||
MatchHead = #router{name = '$1', _ = '_'},
|
||||
Result = ['$_'],
|
||||
Guard = guard(Specs),
|
||||
Result = ['$_'],
|
||||
{MatchHead, Guard, Result}.
|
||||
|
||||
guard(Specs) when is_list(Specs) ->
|
||||
guard(Specs, []).
|
||||
guard([], Guard) ->
|
||||
Guard;
|
||||
guard([{name, Name}|Tail], Guard) when Name =/= <<"">> ->
|
||||
guard(Tail, [{'=:=', '$1', Name}|Guard]);
|
||||
guard([_|Tail], Guard) ->
|
||||
guard(Tail, Guard).
|
||||
|
||||
%% 获取app表的数据大小
|
||||
table_size() ->
|
||||
mnesia:table_info(router, size).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
sort(Routers) when is_list(Routers) ->
|
||||
lists:sort(fun(#router{router_id = Id0}, #router{router_id = Id1}) -> Id0 < Id1 end, Routers).
|
||||
|
||||
to_map(#router{router_id = RouterId, name = Name, status = Status, endpoint = Endpoint}) ->
|
||||
EndpointInfo = case Endpoint of
|
||||
undefined ->
|
||||
#{};
|
||||
#http_endpoint{url = Url} ->
|
||||
#{
|
||||
<<"type">> => <<"http">>,
|
||||
<<"url">> => Url
|
||||
};
|
||||
#kafka_endpoint{} ->
|
||||
#{}
|
||||
end,
|
||||
#{
|
||||
<<"router_id">> => RouterId,
|
||||
<<"name">> => Name,
|
||||
<<"status">> => Status,
|
||||
<<"endpoint">> => EndpointInfo
|
||||
}.
|
||||
@ -1,100 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%% 场景部署关系表
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(scenario_deploy_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-define(TAB_NAME, scenario_deploy).
|
||||
|
||||
%% API
|
||||
-export([get_host_deploy_list/1, get_scenario_deploy_list/1, add_deploy/1, change_status/2, delete/1, table_size/0]).
|
||||
-export([to_map/1]).
|
||||
|
||||
-spec get_host_deploy_list(HostId :: binary()) -> {ok, List :: [#scenario_deploy{}]} | {error, Reason :: any()}.
|
||||
get_host_deploy_list(HostId) when is_binary(HostId) ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#scenario_deploy.host_id =:= HostId]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Items} ->
|
||||
{ok, sort(Items)};
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec get_scenario_deploy_list(ScenarioId :: integer()) -> {ok, List :: [#scenario_deploy{}]} | {error, Reason :: any()}.
|
||||
get_scenario_deploy_list(ScenarioId) when is_integer(ScenarioId) ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#scenario_deploy.scenario_id =:= ScenarioId]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Items} ->
|
||||
{ok, sort(Items)};
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec add_deploy(Deploy :: #scenario_deploy{}) -> ok | {error, Reason :: any()}.
|
||||
add_deploy(Deploy = #scenario_deploy{}) ->
|
||||
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Deploy, write) end) of
|
||||
{atomic, _} ->
|
||||
ok;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec change_status(DeployId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
|
||||
change_status(DeployId, Status) when is_integer(DeployId), is_integer(Status) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(?TAB_NAME, DeployId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"deploy not found">>);
|
||||
[Deploy] ->
|
||||
mnesia:write(?TAB_NAME, Deploy#scenario_deploy{status = Status}, write)
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec delete(DeployId :: binary()) -> ok | {error, Reason :: any()}.
|
||||
delete(DeployId) when is_integer(DeployId) ->
|
||||
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, DeployId, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取app表的数据大小
|
||||
table_size() ->
|
||||
mnesia:table_info(?TAB_NAME, size).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
sort(DeployList) when is_list(DeployList) ->
|
||||
lists:sort(fun(#scenario_deploy{deploy_id = Id0}, #scenario_deploy{deploy_id = Id1}) -> Id0 < Id1 end, DeployList).
|
||||
|
||||
to_map(#scenario_deploy{deploy_id = DeployId, scenario_id = ScenarioId, host_id = HostId, create_ts = CreateTs, update_ts = UpdateTs, status = Status}) ->
|
||||
#{
|
||||
<<"deploy_id">> => DeployId,
|
||||
<<"scenario_id">> => ScenarioId,
|
||||
<<"host_id">> => HostId,
|
||||
<<"create_ts">> => CreateTs,
|
||||
<<"update_ts">> => UpdateTs,
|
||||
<<"status">> => Status
|
||||
}.
|
||||
@ -1,115 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(scenario_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-define(TAB_NAME, scenario).
|
||||
|
||||
%% API
|
||||
-export([get_scenario/1, get_scenario_list/3, add_scenario/1, change_status/2, delete/1, table_size/0]).
|
||||
-export([to_map/1, match_spec/1]).
|
||||
|
||||
-spec get_scenario(ScenarioId :: integer()) -> {ok, #scenario{}} | undefined.
|
||||
get_scenario(ScenarioId) when is_integer(ScenarioId) ->
|
||||
case mnesia:dirty_read(?TAB_NAME, ScenarioId) of
|
||||
[Scenario = #scenario{}] ->
|
||||
{ok, Scenario};
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
%% 获取app信息
|
||||
-spec get_scenario_list(Filter :: any(), Start :: integer(), Limit :: integer()) ->
|
||||
{ok, Items :: list(), TotalNum :: integer()} |
|
||||
{error, Reason :: any()}.
|
||||
get_scenario_list(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
|
||||
Scenarios0 = mnesia:dirty_select(?TAB_NAME, [Spec]),
|
||||
Scenarios = sort(Scenarios0),
|
||||
Len = length(Scenarios),
|
||||
case Len >= Start + 1 of
|
||||
true ->
|
||||
{ok, lists:sublist(Scenarios, Start + 1, Limit), Len};
|
||||
false ->
|
||||
{ok, [], Len}
|
||||
end.
|
||||
|
||||
-spec add_scenario(Scenario :: #scenario{}) -> ok | {error, Reason :: any()}.
|
||||
add_scenario(Scenario = #scenario{}) ->
|
||||
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Scenario, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec change_status(ScenarioId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
|
||||
change_status(ScenarioId, Status) when is_integer(ScenarioId), is_integer(Status) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(scenario, ScenarioId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"scenario not found">>);
|
||||
[Scenario] ->
|
||||
mnesia:write(?TAB_NAME, Scenario#scenario{status = Status}, write)
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec delete(ScenarioId :: binary()) -> ok | {error, Reason :: any()}.
|
||||
delete(ScenarioId) when is_integer(ScenarioId) ->
|
||||
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, ScenarioId, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取查询的过滤条件
|
||||
match_spec(Specs) when is_list(Specs) ->
|
||||
MatchHead = #scenario{name = '$1', _ = '_'},
|
||||
Result = ['$_'],
|
||||
Guard = guard(Specs),
|
||||
Result = ['$_'],
|
||||
{MatchHead, Guard, Result}.
|
||||
|
||||
guard(Specs) when is_list(Specs) ->
|
||||
guard(Specs, []).
|
||||
guard([], Guard) ->
|
||||
Guard;
|
||||
guard([{name, Name}|Tail], Guard) when Name =/= <<"">> ->
|
||||
guard(Tail, [{'=:=', '$1', Name}|Guard]);
|
||||
guard([_|Tail], Guard) ->
|
||||
guard(Tail, Guard).
|
||||
|
||||
%% 获取app表的数据大小
|
||||
table_size() ->
|
||||
mnesia:table_info(scenario, size).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
sort(Scenarios) when is_list(Scenarios) ->
|
||||
lists:sort(fun(#scenario{scenario_id = Id0}, #scenario{scenario_id = Id1}) -> Id0 < Id1 end, Scenarios).
|
||||
|
||||
to_map(#scenario{scenario_id = ScenarioId, name = Name, desc = Desc, rule = Rule, update_ts = UpdateTs, status = Status}) ->
|
||||
#{
|
||||
<<"scenario_id">> => ScenarioId,
|
||||
<<"name">> => Name,
|
||||
<<"desc">> => Desc,
|
||||
<<"rule">> => Rule,
|
||||
<<"update_ts">> => UpdateTs,
|
||||
<<"status">> => Status
|
||||
}.
|
||||
@ -1,128 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(service_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
%% API
|
||||
-export([get_host_services/1, get_service/2, add_service/1, change_status/2, delete/1, table_size/0]).
|
||||
-export([update_metric/2, to_map/1]).
|
||||
|
||||
get_service(HostId, ServiceName) when is_binary(HostId), is_binary(ServiceName) ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(service), E#service.host_id =:= HostId, E#service.name =:= ServiceName]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
[Service] ->
|
||||
{ok, Service};
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
get_host_services(HostId) when is_binary(HostId) ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(service), E#service.host_id =:= HostId]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Services} when is_list(Services) ->
|
||||
{ok, Services};
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
add_service(Service = #service{}) ->
|
||||
case mnesia:transaction(fun() -> mnesia:write(service, Service, write) end) of
|
||||
{atomic, _} ->
|
||||
ok;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
change_status(ServiceId, Status) when is_binary(ServiceId), is_integer(Status) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(service, ServiceId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"host not found">>);
|
||||
[Service] ->
|
||||
NService = Service#service{status = Status},
|
||||
mnesia:write(service, NService, write)
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
update_metric(ServiceId, Metrics) when is_binary(ServiceId), is_list(Metrics) ->
|
||||
Fun = fun() ->
|
||||
case mnesia:read(service, ServiceId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"service not found">>);
|
||||
[Service = #service{metrics = Metrics}] ->
|
||||
NService = Service#service{metrics = Metrics},
|
||||
mnesia:write(service, NService, write)
|
||||
end
|
||||
end,
|
||||
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec delete(HostId :: binary()) -> ok | {error, Reason :: any()}.
|
||||
delete(ServiceId) when is_binary(ServiceId) ->
|
||||
case mnesia:transaction(fun() -> mnesia:delete(service, ServiceId, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取app表的数据大小
|
||||
table_size() ->
|
||||
mnesia:table_info(service, size).
|
||||
|
||||
to_map(#service{service_id = ServiceId, host_id = HostId, name = Name, category = Category, apply_object = ApplyObject,
|
||||
version = Version, execute_count = ExecuteCount, deploy_ts = DeployTs, metrics = Metrics, status = Status}) ->
|
||||
|
||||
Metrics1 = lists:map(fun(#service_metric{symbol = MetricSymbol, name = MetricName, last_value = LastValue, unit = Unit, queue = Queue, update_ts = UpdateTs}) ->
|
||||
Q = lists:map(fun({Ts, Val}) -> #{<<"time">> => Ts, <<"value">> => Val} end, queue:to_list(Queue)),
|
||||
#{
|
||||
<<"symbol">> => MetricSymbol,
|
||||
<<"name">> => MetricName,
|
||||
<<"last_value">> => LastValue,
|
||||
<<"queue">> => Q,
|
||||
<<"unit">> => Unit,
|
||||
<<"update_ts">> => UpdateTs
|
||||
}
|
||||
end, Metrics),
|
||||
|
||||
#{
|
||||
<<"service_id">> => ServiceId,
|
||||
<<"host_id">> => HostId,
|
||||
<<"name">> => Name,
|
||||
<<"category">> => Category,
|
||||
<<"apply_object">> => ApplyObject,
|
||||
<<"version">> => Version,
|
||||
<<"execute_count">> => ExecuteCount,
|
||||
<<"deploy_ts">> => DeployTs,
|
||||
<<"metrics">> => Metrics1,
|
||||
<<"status">> => Status
|
||||
}.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% helper methods
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@ -1,214 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author licheng5
|
||||
%%% @copyright (C) 2021, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 4月 2021 下午4:38
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(terminal_model).
|
||||
-author("licheng5").
|
||||
-include("iot.hrl").
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-define(TAB_NAME, terminal).
|
||||
|
||||
%% API
|
||||
-export([next_id/0]).
|
||||
-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]).
|
||||
|
||||
%% 生成信息的id信息
|
||||
-spec next_id() -> Id :: integer().
|
||||
next_id() ->
|
||||
id_generator_model:new_terminal_id().
|
||||
|
||||
-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()}.
|
||||
get_all_terminals() ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME)]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Terminals} when is_list(Terminals) ->
|
||||
{ok, sort(Terminals)};
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec get_host_terminals(HostId :: binary()) -> {ok, Terminals :: [#terminal{}]} | {error, Reason :: any()}.
|
||||
get_host_terminals(HostId) when is_binary(HostId) ->
|
||||
Fun = fun() ->
|
||||
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#terminal.host_id =:= HostId]),
|
||||
qlc:e(Q)
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Terminals} when is_list(Terminals) ->
|
||||
{ok, sort(Terminals)};
|
||||
{aborted, Error} ->
|
||||
{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_stat() -> {ok, {Total :: integer(), Stat :: #{}}} | {error, Reason :: any()}.
|
||||
get_stat() ->
|
||||
Fun = fun() ->
|
||||
mnesia:foldl(fun(#terminal{status = Status}, {Total, Acc}) ->
|
||||
Num = maps:get(Status, Acc, 0),
|
||||
{Total + 1, Acc#{Status => Num + 1}}
|
||||
end, {0, #{}}, ?TAB_NAME)
|
||||
end,
|
||||
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, Stat} ->
|
||||
{ok, Stat};
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec add_terminal(Terminal :: #terminal{}) -> ok | {error, Reason :: any()}.
|
||||
add_terminal(Terminal = #terminal{}) ->
|
||||
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Terminal, write) end) of
|
||||
{atomic, _} ->
|
||||
ok;
|
||||
{aborted, Error} ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
-spec update_terminal(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() ->
|
||||
case mnesia:read(?TAB_NAME, TerminalId) of
|
||||
[] ->
|
||||
mnesia:abort(<<"terminal not found">>);
|
||||
[Terminal] ->
|
||||
mnesia:write(?TAB_NAME, Terminal#host{status = Status}, write)
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(Fun) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec delete(TerminalId :: integer()) -> ok | {error, Reason :: any()}.
|
||||
delete(TerminalId) when is_integer(TerminalId) ->
|
||||
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, TerminalId, write) end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% 获取app表的数据大小
|
||||
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
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
sort(Terminals) when is_list(Terminals) ->
|
||||
lists:sort(fun(#terminal{terminal_id = Id0}, #terminal{terminal_id = Id1}) -> Id0 < Id1 end, Terminals).
|
||||
|
||||
to_map(#terminal{terminal_id = TerminalId, host_id = HostId, serial_number = SerialNumber, name = Name, code = Code, access_protocol = AccessProtocol,
|
||||
product_id = ProductId, vendor_id = VendorId, model = Model, cell_id = CellId, status = Status, update_ts = UpdateTs}) ->
|
||||
#{
|
||||
<<"terminal_id">> => TerminalId,
|
||||
<<"host_id">> => HostId,
|
||||
<<"serial_number">> => SerialNumber,
|
||||
<<"name">> => Name,
|
||||
<<"code">> => Code,
|
||||
<<"access_protocol">> => AccessProtocol,
|
||||
<<"product_id">> => ProductId,
|
||||
<<"vendor_id">> => VendorId,
|
||||
<<"model">> => Model,
|
||||
<<"cell_id">> => CellId,
|
||||
<<"status">> => Status,
|
||||
<<"update_ts">> => UpdateTs
|
||||
}.
|
||||
178
apps/iot/src/mysql_client.erl
Normal file
178
apps/iot/src/mysql_client.erl
Normal file
@ -0,0 +1,178 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author aresei
|
||||
%%% @copyright (C) 2018, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 29. 九月 2018 17:01
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mysql_client).
|
||||
-author("aresei").
|
||||
|
||||
-define(POOL_NAME, mysql_pool).
|
||||
|
||||
%% API
|
||||
-export([get_row/1, get_row/2, get_all/1, get_all/2]).
|
||||
-export([update/3, update_by/1, update_by/2, insert/3]).
|
||||
-export([test/0]).
|
||||
|
||||
test() ->
|
||||
|
||||
Result = get_row(<<"select * from Carport limit 10">>),
|
||||
lager:info("[mysql_client] the result is: ~p", [Result]),
|
||||
|
||||
Result1 = get_all(<<"select * from Carport limit 10">>),
|
||||
lager:info("[mysql_client] the result is: ~p", [Result1]),
|
||||
|
||||
InsertResult = insert(<<"CarInfo">>, [{<<"owner_name">>, <<"anlicheng">>}], false),
|
||||
lager:info("[mysql_client] the insert id is: ~p", [InsertResult]),
|
||||
|
||||
UpdateResult = update_by(<<"update CarInfo set owner_name = AiYaLing123 where car_id = ? limit 1">>, [1]),
|
||||
|
||||
lager:info("[mysql_client] the update result is: ~p", [UpdateResult]),
|
||||
|
||||
ok.
|
||||
|
||||
%% 从数据库中查找一行记录
|
||||
-spec get_row(Sql::binary()) -> {ok, Record::map()} | undefined.
|
||||
get_row(Sql) when is_binary(Sql) ->
|
||||
lager:debug("[mysql_client] the get_row sql is: ~p", [Sql]),
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, Sql) of
|
||||
{ok, Names, [Row | _]} ->
|
||||
{ok, maps:from_list(lists:zip(Names, Row))};
|
||||
{ok, _, []} ->
|
||||
undefined;
|
||||
Error ->
|
||||
lager:warning("[mysql_client] get error: ~p", [Error]),
|
||||
undefined
|
||||
end
|
||||
end).
|
||||
|
||||
-spec get_row(Sql::binary(), Params::list()) -> {ok, Record::map()} | undefined.
|
||||
get_row(Sql, Params) when is_binary(Sql), is_list(Params) ->
|
||||
lager:debug("[mysql_client] the get_row sql is: ~p, params: ~p", [Sql, Params]),
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, Sql, Params) of
|
||||
{ok, Names, [Row | _]} ->
|
||||
{ok, maps:from_list(lists:zip(Names, Row))};
|
||||
{ok, _, []} ->
|
||||
undefined;
|
||||
Error ->
|
||||
lager:warning("[mysql_client] get error: ~p", [Error]),
|
||||
undefined
|
||||
end
|
||||
end).
|
||||
|
||||
-spec get_all(Sql::binary()) -> {ok, Rows::list()} | {error, Reason :: any()}.
|
||||
get_all(Sql) when is_binary(Sql) ->
|
||||
lager:debug("[mysql_client] the get_all sql is: ~p", [Sql]),
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, Sql) of
|
||||
{ok, Names, Rows} ->
|
||||
{ok, lists:map(fun(Row) -> maps:from_list(lists:zip(Names, Row)) end, Rows)};
|
||||
Error ->
|
||||
lager:warning("[mysql_client] get error: ~p", [Error]),
|
||||
Error
|
||||
end
|
||||
end).
|
||||
|
||||
-spec get_all(Sql::binary(), Params::list()) -> {ok, Rows::list()} | {error, Reason::any()}.
|
||||
get_all(Sql, Params) when is_binary(Sql), is_list(Params) ->
|
||||
lager:debug("[mysql_client] the get_all sql is: ~p, params: ~p", [Sql, Params]),
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, Sql, Params) of
|
||||
{ok, Names, Rows} ->
|
||||
{ok, lists:map(fun(Row) -> maps:from_list(lists:zip(Names, Row)) end, Rows)};
|
||||
Error ->
|
||||
lager:warning("[mysql_client] get error: ~p", [Error]),
|
||||
{ok, []}
|
||||
end
|
||||
end).
|
||||
|
||||
-spec insert(Table :: binary(), Fields :: map() | list(), boolean()) -> ok | {ok, InsertId :: integer()} | {error, Reason :: any()}.
|
||||
insert(Table, Fields, FetchInsertId) when is_binary(Table), is_map(Fields), is_boolean(FetchInsertId) ->
|
||||
insert(Table, maps:to_list(Fields), FetchInsertId);
|
||||
insert(Table, Fields, FetchInsertId) when is_binary(Table), is_list(Fields), is_boolean(FetchInsertId) ->
|
||||
{Keys, Values} = kvs(Fields),
|
||||
|
||||
FieldSql = iolist_to_binary(lists:join(<<", ">>, Keys)),
|
||||
Placeholders = lists:duplicate(length(Keys), <<"?">>),
|
||||
ValuesPlaceholder = iolist_to_binary(lists:join(<<", ">>, Placeholders)),
|
||||
|
||||
Sql = <<"INSERT INTO ", Table/binary, "(", FieldSql/binary, ") VALUES(", ValuesPlaceholder/binary, ")">>,
|
||||
lager:debug("[mysql_client] the insert sql is: ~p, params: ~p", [Sql, Values]),
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, Sql, Values) of
|
||||
ok ->
|
||||
case FetchInsertId of
|
||||
true ->
|
||||
InsertId = mysql:insert_id(ConnPid),
|
||||
{ok, InsertId};
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
end).
|
||||
|
||||
update_by(UpdateSql) when is_binary(UpdateSql) ->
|
||||
lager:debug("[mysql_client] updateBySql sql: ~p", [UpdateSql]),
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, UpdateSql) of
|
||||
ok ->
|
||||
AffectedRows = mysql:affected_rows(ConnPid),
|
||||
{ok, AffectedRows};
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
end).
|
||||
|
||||
-spec update_by(UpdateSql :: binary(), Params :: list()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
|
||||
update_by(UpdateSql, Params) when is_binary(UpdateSql) ->
|
||||
lager:debug("[mysql_client] updateBySql sql: ~p, params: ~p", [UpdateSql, Params]),
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, UpdateSql, Params) of
|
||||
ok ->
|
||||
AffectedRows = mysql:affected_rows(ConnPid),
|
||||
{ok, AffectedRows};
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
end).
|
||||
|
||||
-spec update(Sql :: binary(), Fields :: map(), WhereFields :: map()) -> {ok, AffectedRows::integer()} | {error, Reason::any()}.
|
||||
update(Table, Fields, WhereFields) when is_binary(Table), is_map(Fields), is_map(WhereFields) ->
|
||||
%% 拼接set
|
||||
{SetKeys, SetVals} = kvs(Fields),
|
||||
SetKeys1 = lists:map(fun(K) when is_binary(K) -> <<"`", K/binary, "` = ?">> end, SetKeys),
|
||||
SetSql = iolist_to_binary(lists:join(<<", ">>, SetKeys1)),
|
||||
|
||||
%% 拼接where
|
||||
{WhereKeys, WhereVals} = kvs(WhereFields),
|
||||
WhereKeys1 = lists:map(fun(K) when is_binary(K) -> <<"`", K/binary, "` = ?">> end, WhereKeys),
|
||||
WhereSql = iolist_to_binary(lists:join(<<" AND ">>, WhereKeys1)),
|
||||
|
||||
Params = SetVals ++ WhereVals,
|
||||
|
||||
Sql = <<"UPDATE ", Table/binary, " SET ", SetSql/binary, " WHERE ", WhereSql/binary>>,
|
||||
lager:debug("[mysql_client] update sql is: ~p, params: ~p", [Sql, Params]),
|
||||
|
||||
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
|
||||
case mysql:query(ConnPid, Sql, Params) of
|
||||
ok ->
|
||||
AffectedRows = mysql:affected_rows(ConnPid),
|
||||
{ok, AffectedRows};
|
||||
Error ->
|
||||
lager:error("[mysql_client] update sql: ~p, params: ~p, get a error: ~p", [Sql, Params, Error]),
|
||||
Error
|
||||
end
|
||||
end).
|
||||
|
||||
-spec kvs(Fields :: map() | list()) -> {Keys :: list(), Values :: list()}.
|
||||
kvs(Fields) when is_map(Fields) ->
|
||||
kvs(maps:to_list(Fields));
|
||||
kvs(Fields) when is_list(Fields) ->
|
||||
{Keys0, Values0} = lists:foldl(fun({K, V}, {Acc0, Acc1}) -> {[K|Acc0], [V|Acc1]} end, {[], []}, Fields),
|
||||
{lists:reverse(Keys0), lists:reverse(Values0)}.
|
||||
@ -1,7 +1,7 @@
|
||||
[
|
||||
{iot, [
|
||||
{http_server, [
|
||||
{port, 8080},
|
||||
{port, 18080},
|
||||
{acceptors, 500},
|
||||
{max_connections, 10240},
|
||||
{backlog, 10240}
|
||||
@ -9,17 +9,44 @@
|
||||
|
||||
%% 目标服务器地址
|
||||
{emqx_server, [
|
||||
{host, {103, 113, 157, 7}},
|
||||
{host, {39, 98, 184, 67}},
|
||||
{port, 1883},
|
||||
{tcp_opts, []},
|
||||
{username, "test"},
|
||||
{password, "test1234"},
|
||||
{keepalive, 86400},
|
||||
{retry_interval, 5}
|
||||
]},
|
||||
|
||||
{pools, [
|
||||
%% mysql连接池配置
|
||||
{mysql_pool,
|
||||
[{size, 10}, {max_overflow, 20}, {worker_module, mysql}],
|
||||
[
|
||||
{host, {39, 98, 184, 67}},
|
||||
{port, 3306},
|
||||
{user, "nannonguser"},
|
||||
{password, "nannong@Fe7w"},
|
||||
{database, "nannong"},
|
||||
{queries, [<<"set names utf8">>]}
|
||||
]
|
||||
},
|
||||
|
||||
%% influxdb数据库配置
|
||||
{influx_pool,
|
||||
[{size, 100}, {max_overflow, 200}, {worker_module, influx_client}],
|
||||
[
|
||||
{host, "39.98.184.67"},
|
||||
{port, 8086},
|
||||
{token, <<"HcnjcIaoPWGxnDOHpW2HV-xBsZSyqtC2UR98SO8LNgNhczuNyU6PQ4s1Blfz7vKy-CB_A-Kyjzed_EX7Ak2RAA==">>}
|
||||
]
|
||||
}
|
||||
|
||||
]}
|
||||
|
||||
]},
|
||||
|
||||
|
||||
%% 系统日志配置,系统日志为lager, 支持日志按日期自动分割
|
||||
{lager, [
|
||||
{colored, true},
|
||||
|
||||
299
docs/host-mqtt-jiaohu.md
Normal file
299
docs/host-mqtt-jiaohu.md
Normal file
@ -0,0 +1,299 @@
|
||||
# 交互流程数据格式说明
|
||||
|
||||
## 主题说明
|
||||
|
||||
### system/upstream qos = 1
|
||||
用于主机注册到平台
|
||||
|
||||
### host/upstream/$uuid qos = 1
|
||||
用于主机向平台汇报数据
|
||||
### host/downstream/$uuid qos = 2
|
||||
用于平台向主机下发所有的指令信息
|
||||
|
||||
## 需要注意的点
|
||||
1. 主机的uuid是采用主动发现的机制,因此host主机在启动时需要先subscribe主题: host/downstream/${uuid}
|
||||
|
||||
## register主机注册会话
|
||||
|
||||
1. 请求: 设备端向主题`system/upstream`发送注册信息
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "register",
|
||||
"params": {
|
||||
"uuid": "$uuid",
|
||||
"os_version": "系统版本",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. 响应: `host/downstream/$uuid`
|
||||
|
||||
t = 0
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0 | 1, // 0表示失败,1:表示成功
|
||||
// 内容
|
||||
"m": "$bytes"
|
||||
}
|
||||
```
|
||||
|
||||
## create_session主机注册会话
|
||||
|
||||
register的时候,设备端向主题`host/upstream/${uuid}`发送注册信息,由于topic里面已经携带了主机标识,因此body里面不需要$uuid, 该信息格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "create_session",
|
||||
"params": {
|
||||
"pub_key": "该客户端自己生成的公钥"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中,`pub_key`表示客户端自身的公钥,该公钥用于与服务端交换aes密钥。
|
||||
|
||||
需要注意,这里的交互逻辑有变更(topic: host/downstream/$uuid)
|
||||
1. 当主机未激活时,返回: #{<<"a">> => false, <<"aes">> => <<"">>},
|
||||
2. 当主机已经激活时,返回: #{<<"a">> => true, <<"aes">> => Aes}
|
||||
3. 当主机超过一定的时间没有收到任何返回的时候,需要重试执行create_session操作
|
||||
|
||||
t = 10
|
||||
|
||||
```json
|
||||
{
|
||||
"a": true/false, // 表示是授权还是拒绝授权
|
||||
"aes": "", // 如果a字段为true,表示服务端允许授权,该aes为一个加密字符串,以后的通信,服务端和设备端都使用该aes密钥进行加解密。
|
||||
}
|
||||
```
|
||||
|
||||
## 主机授权消息格式
|
||||
|
||||
t = 8
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"auth": true/false, // true表示授权,此时aes的值不为空;false表示取消授权
|
||||
"aes": "",
|
||||
"reply": {
|
||||
"topic": "", // 主机端操作成功后需要回写的topic名称
|
||||
"assoc": "" // 关联的信息,回写的时候需要带上
|
||||
}
|
||||
}
|
||||
|
||||
// 回写的消息格式, 其中topic为: reply.topic的值
|
||||
|
||||
{
|
||||
"code": 0 | 1, // 0表示操作失败,1表示成功
|
||||
messge: "",
|
||||
assoc: "下发给你值"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 数据上传结构
|
||||
设备端在采集到数据之后,会向topic为`host/upstream/$uuid`发送消息,消息格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"methods": "data",
|
||||
"params": "$bytes"
|
||||
}
|
||||
```
|
||||
|
||||
其中`params`代表具体的采集信息,这部分数据通过与服务端交互商量的: base64:encode(aes加密),加密之前是一个列表,列表里面的数据格式如下:
|
||||
|
||||
```json:
|
||||
[
|
||||
{
|
||||
"service_name": "从该设备端的哪个服务采集的数据",
|
||||
"node_name": "节点名称", insert到influxdb的时候就是表名
|
||||
"at": int, 精确到毫秒
|
||||
// 该微服务采集的数据,是一个包含map的列表类型,map的内容可以由微服务自己指定
|
||||
// 目前一般的格式是"metric-name": $value样式的数据
|
||||
"fields": [
|
||||
{
|
||||
"name1": "test"
|
||||
"name2": 124,
|
||||
"name3": false,
|
||||
"device_id": $uuid 非设备产生的device_id为空
|
||||
}
|
||||
],
|
||||
// 微服务自身可以生成tag,用于微服务指定自己的一些性质,目前使用得不多,以后可以扩展,
|
||||
// 是一个map[string]string类型的数据
|
||||
"tags": {
|
||||
"tag1": "value1",
|
||||
"tag2", "value2"
|
||||
}
|
||||
// todo 在insert数据到influxdb的时候需要增加service_name + host_uuid
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 命令下发结构
|
||||
命令下发,用于服务端或者其他的系统,通过调用接口,向设备端发送消息,设备端会监听`host/downstream/$uuid`的消息。
|
||||
服务端在发送之前,应先用该客户端的aes密钥进行加密,将加密之后的二进制数据发送到该topic。
|
||||
TODO 命令下发是需要增加当前的时间戳,host主机用来协调任务的一致性
|
||||
|
||||
加密前的消息结构如下:
|
||||
// 消息类型,目前支持四种消息类型:
|
||||
// 1代表参数下发,就是向该设备端的微服务发送消息,该消息会辗转发送给微服务进行处理,比如,设置modbus微服务的波特率等消息
|
||||
// 2代表采集向下发,比如,设置某个设备短上的modbus微服务采集某个地址的数据
|
||||
// 3代表下发微服务文件。
|
||||
// 4代表下发数据流图,这个指令用于设置设备端上各个微服务之间的逐句流转。
|
||||
|
||||
占用数据项的第一个字节
|
||||
|
||||
"t": 1|2|3|4,
|
||||
|
||||
```json
|
||||
{
|
||||
// 针对不同的命令类型,这个字段里的`to`和`m`数据有所不同,具体在下面的小节描述
|
||||
// 任务id,服务端在下发数据的时候,需要生成一个唯一的uuid,
|
||||
// 用于标识一个任务
|
||||
"t_id": "任务id",
|
||||
// 表示发给哪个微服务,这里是服务的标识,即服务名称
|
||||
"to": "",
|
||||
// 命令执行的超时时间,单位为秒
|
||||
"t": 10,
|
||||
// 实际内容
|
||||
"m": "$bytes",
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
下面介绍几种下发类型:
|
||||
|
||||
### 参数下发的结构
|
||||
对于参数下发,下发内容中的m为一个`map[string]interface{}`结构,用于向某个微服务发送参数,具体参数内容由微服务的参数配置提供。
|
||||
|
||||
### 采集项下发的结构
|
||||
采集项下发时,下发内容中的m为一个`[]map[string]interface{}`结构的列表,每一个条目是一个采集项内容,具体采集向内容由微服务的采集项配置提供。
|
||||
|
||||
### 微服务下发的结构
|
||||
在微服务下发中,`to`字段会被忽略,可以填写空字符串,而m字段为json化之后的数据,json化之前结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"f": "微服务名",
|
||||
"v": "微服务版本"
|
||||
"k": "微服务下载的token"
|
||||
"md5": "微服务的md5值,用于验证下载完整性"
|
||||
// ms表示是微服务,config表示配置文件,self表示efka的新版本
|
||||
"t": "ms|config|self"
|
||||
// o代表oldversion,老版本,如果t为ms,且o不为空字符串,
|
||||
// 则表示要升级微服务版本,老版本的内容会被删除和替换。
|
||||
"o": "old-version"
|
||||
}
|
||||
```
|
||||
|
||||
## ping结构
|
||||
ping结构会上传到topic为"host/upstream/$uuid", 结构体由msgpack格式编码, ping结构每隔20秒发送一次, 其中params的数据格式为: base64:encode(aes("加密"))
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "ping",
|
||||
"params": {
|
||||
// 硬件信息,目前有剩余内存,剩余磁盘和cpu负载
|
||||
// 剩余内存,单位为mb
|
||||
|
||||
"memory": {
|
||||
"total": 1024, // 内存数,mb
|
||||
"used": "$int" // 剩余内存数
|
||||
},
|
||||
"disk": {
|
||||
"total": 1024, // 硬盘容量GB
|
||||
"used": "$int" // 剩余硬盘内容GB
|
||||
},
|
||||
|
||||
"cpu_load": $float, // 浮点数
|
||||
"cpu_temperature": $float // 稳定信息
|
||||
"cpu_core": $int,
|
||||
|
||||
"boot_time": 2000, // 启动时间
|
||||
"efka_version": "1.0.0", // 客户端版本
|
||||
"kernel_arch": "arm64", // 客户端硬件架构
|
||||
"province": "", // 所在省
|
||||
"city": "", // 所在市
|
||||
"adcode": 100, // 所在城市的编号
|
||||
"ips": [
|
||||
"ip地址1",
|
||||
"ip地址2"
|
||||
],
|
||||
// 接口信息
|
||||
"interfaces": [
|
||||
{
|
||||
"status": 0|1, "接口状态,0离线,1在线"
|
||||
"name": "接口名称",
|
||||
"desc": "接口描述",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 命令下发步骤上报结构
|
||||
在任务下发之后,设备端会根据命令到哪个环节,进行步骤上报,上报的消息写入到topic为`host/upstream/$uuid`,上报结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "feedback_step",
|
||||
"params": {
|
||||
"task_id": "任务的task id",
|
||||
// sc为step code,具体地:
|
||||
// 0代表该任务开始了,服务端创建该任务之后,是这个代码
|
||||
// 1代表任务被分发了,服务端向nats(mqtt)发送消息之后,是这个代码
|
||||
// 2代表任务被设备端接收到了
|
||||
// 3代表该任务已经被发送给微服务进行处理了
|
||||
// 4代表该任务已经被微服务收到了,微服务正在处理
|
||||
// 5代表任务已经完成,微服务已经处理完成。
|
||||
"code": $int
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了这个步骤,最后任务超时等情况,就可以知道任务卡在哪里。
|
||||
|
||||
## 命令下发结果上报结构
|
||||
任务在微服务处理完成之后,设备端会向服务端反馈任务执行的结果。该结果会写入`host/upstream/$uuid`,上报的结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "feedback_result",
|
||||
"params": {
|
||||
"task_id": "任务id",
|
||||
// unix nano类型
|
||||
"time": $int,
|
||||
// 返回的结果码,0代表成功,其他代表出错
|
||||
"code": $int,
|
||||
"reason": "任务执行的结果",
|
||||
"error": "错误消息,当c为非0时,这个字段会表示出错消息",
|
||||
// 返回任务类型,1表示任务是微服务下发,0代表是命令下发
|
||||
"type": 0 | 1,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## inform结构
|
||||
inform用于客户端主动通知服务端本地事情的发生,比如,自身的某个微服务下线了,就会发送一个inform信息给服务端。 topic: host/upstream/$uuid
|
||||
|
||||
inform信息会发送给``, 结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "inform",
|
||||
"params": {
|
||||
"at": $int64,
|
||||
// 微服务信息
|
||||
"services": [{
|
||||
"name": "微服务名称",
|
||||
"version": "微服务版本",
|
||||
"version_copy": "微服务副本",
|
||||
// 微服务是否在线,0表示离线,1表示在线
|
||||
"status": 0|1
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1,233 +0,0 @@
|
||||
# 交互流程数据格式说明
|
||||
|
||||
## register
|
||||
register的时候,设备端向`server.register`发送注册信息,该信息格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"c_id": "client_id",
|
||||
"r": "该客户端自己生成的公钥",
|
||||
"m": $object,
|
||||
}
|
||||
```
|
||||
|
||||
其中,`c_id`代表客户端本身的id,用于唯一标识一个客户端,`r`表示客户端自身的公钥,该公钥用于与服务端交换aes密钥。
|
||||
|
||||
`m`是设备端的一些固有属性,目前结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"cpu_core": 4, // 设备cpu核心数
|
||||
"memory": 1024, // 内存数,MB
|
||||
"disk": 1024, // 硬盘容量,GB
|
||||
"boot_time": 2000, // 启动时间
|
||||
"efka_version": "1.0.0", // 客户端版本
|
||||
"kernel_arch": "arm64", // 客户端硬件架构
|
||||
"province": "", // 所在省
|
||||
"city": "", // 所在市
|
||||
"adcode": 100, // 所在城市的编号
|
||||
"IPv4 1": "ip地址1",
|
||||
"IPv4 2": "ip地址2",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
在服务端,在第一次收到该设备端的注册请求之后,会先将该信息保存下来,当用户在操作界面上对该设备进行授权之后,服务端会生成一个AES密钥,用该设备端的公钥进行加密,然后向`client.auth.$clientid`写入授权信息,客户端收到之后,可以得到本身的授权情况。服务端用RSA公钥加密的结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"a": true/false, // 表示是授权还是拒绝授权
|
||||
"aes": "", // 如果a字段为true,表示服务端允许授权,该aes为一个加密字符串,以后的通信,服务端和设备端都使用该aes密钥进行加解密。
|
||||
"reply": "client.reply.$uuid" // 服务端设置的一个客户端回复的topic,如果服务端在5秒之内没有回复,就认为这个动作失败,服务端就会反馈给操作界面的用户。
|
||||
}
|
||||
```
|
||||
|
||||
## 数据上传结构
|
||||
设备端在采集到数据之后,会向`server.data`发送消息,消息格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"c_id": "设备端ID",
|
||||
"d": $bytes
|
||||
}
|
||||
```
|
||||
|
||||
其中`c_id`是自身的`clientid`,`d`代表具体的采集信息,这部分数据通过与服务端交互商量的aes加密,加密之前是一个列表,列表里面的数据格式如下:
|
||||
|
||||
```json:
|
||||
[
|
||||
{
|
||||
"client_id": "设备端ID",
|
||||
"service_name": "从该设备端的哪个服务采集的数据",
|
||||
"time": $time, // 采集时间,unixnano的int64
|
||||
// 该微服务采集的数据,是一个包含map的列表类型,map的内容可以由微服务自己指定
|
||||
// 目前一般的格式是"metric-name": $value样式的数据
|
||||
"data": [
|
||||
{
|
||||
"name1": "test"
|
||||
"name2": 124,
|
||||
"name3": false
|
||||
}
|
||||
],
|
||||
// 微服务自身可以生成tag,用于微服务指定自己的一些性质,目前使用得不多,以后可以扩展,
|
||||
// 是一个map[string]string类型的数据
|
||||
"tag": {
|
||||
"tag1": "value1",
|
||||
"tag2", "value2"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 命令下发结构
|
||||
命令下发,用于服务端或者其他的系统,通过调用接口,向设备端发送消息,设备端会监听`clients.cmd.$id`的消息。
|
||||
|
||||
服务端在发送之前,应先用该客户端的aes密钥进行加密,将加密之后的二进制数据发送到该topic。
|
||||
|
||||
加密前的消息结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
// 消息类型,目前支持四种消息类型:
|
||||
// 1代表参数下发,就是向该设备端的微服务发送消息,该消息会辗转发送给微服务进行处理,比如,设置modbus微服务的波特率等消息
|
||||
// 2代表采集向下发,比如,设置某个设备短上的modbus微服务采集某个地址的数据
|
||||
// 3代表下发微服务文件。
|
||||
// 4代表下发数据流图,这个指令用于设置设备端上各个微服务之间的逐句流转。
|
||||
"t": 1|2|3|4,
|
||||
// 针对不同的命令类型,这个字段里的`to`和`m`数据有所不同,具体在下面的小节描述
|
||||
"b": {
|
||||
// 任务id,服务端在下发数据的时候,需要生成一个唯一的uuid,
|
||||
// 用于标识一个任务
|
||||
"t_id": "任务id",
|
||||
// 表示发给哪个微服务,这里是服务的标识,即服务名称
|
||||
"to": "",
|
||||
// 命令执行的超时时间,单位为秒
|
||||
"t": 10,
|
||||
// 实际内容
|
||||
"m": $bytes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面介绍几种下发类型:
|
||||
|
||||
### 参数下发的结构
|
||||
对于参数下发,下发内容中的m为一个`map[string]interface{}`结构,用于向某个微服务发送参数,具体参数内容由微服务的参数配置提供。
|
||||
|
||||
### 采集项下发的结构
|
||||
采集项下发时,下发内容中的m为一个`[]map[string]interface{}`结构的列表,每一个条目是一个采集项内容,具体采集向内容由微服务的采集项配置提供。
|
||||
|
||||
|
||||
### 微服务下发的结构
|
||||
在微服务下发中,`to`字段会被忽略,可以填写空字符串,而m字段为json化之后的数据,json化之前结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"f": "微服务名",
|
||||
"v": "微服务版本"
|
||||
"k": "微服务下载的token"
|
||||
"md5": "微服务的md5值,用于验证下载完整性"
|
||||
// ms表示是微服务,config表示配置文件,self表示efka的新版本
|
||||
"t": "ms|config|self"
|
||||
// o代表oldversion,老版本,如果t为ms,且o不为空字符串,
|
||||
// 则表示要升级微服务版本,老版本的内容会被删除和替换。
|
||||
"o": "old-version"
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## ping结构
|
||||
ping结构会上传到"server.ping", 结构体由msgpack格式编码, ping结构每隔20秒发送一次
|
||||
|
||||
```json
|
||||
{
|
||||
"c": "client_id",
|
||||
// 这个ping上传的时间
|
||||
"at": $int64,
|
||||
// 硬件信息,目前有剩余内存,剩余磁盘和cpu负载
|
||||
"h": {
|
||||
// 剩余内存,单位为MB
|
||||
"fm": $int
|
||||
// 剩余磁盘,单位为MB
|
||||
"fd": $int,
|
||||
// cpu负载,是一个浮点数
|
||||
"cp": $float
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 命令下发步骤上报结构
|
||||
在任务下发之后,设备端会根据命令到哪个环节,进行步骤上报,上报的消息写入到`server.step.feedback`,上报结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"c_id": "设备端id",
|
||||
"d": "aes加密后的二进制数据"
|
||||
}
|
||||
```
|
||||
|
||||
其中,`d`是具体数据经过aes加密后的二进制数据,加密前的结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"t_id": "任务的task id",
|
||||
|
||||
// sc为step code,具体地:
|
||||
// 0代表该任务开始了,服务端创建该任务之后,是这个代码
|
||||
// 1代表任务被分发了,服务端向nats(mqtt)发送消息之后,是这个代码
|
||||
// 2代表任务被设备端接收到了
|
||||
// 3代表该任务已经被发送给微服务进行处理了
|
||||
// 4代表该任务已经被微服务收到了,微服务正在处理
|
||||
// 5代表任务已经完成,微服务已经处理完成。
|
||||
"sc": $int
|
||||
}
|
||||
```
|
||||
|
||||
有了这个步骤,最后任务超时等情况,就可以知道任务卡在哪里。
|
||||
|
||||
## 命令下发结果上报结构
|
||||
任务在微服务处理完成之后,设备端会向服务端反馈任务执行的结果。该结果会写入`server.result.feedback`,上报的结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"c_id": "设备端id",
|
||||
"d": "aes加密后的二进制数据"
|
||||
}
|
||||
```
|
||||
|
||||
其中,`d`是aes加密后的二进制数据,加密前的结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"t_id": "任务id",
|
||||
// unix nano类型
|
||||
"t": $int,
|
||||
// 返回的结果码,0代表成功,其他代表出错
|
||||
"c": $int,
|
||||
"r": "任务执行的结果",
|
||||
"e": "错误消息,当c为非0时,这个字段会表示出错消息",
|
||||
// 返回任务类型,1表示任务是微服务下发,0代表是命令下发
|
||||
"t": 0 | 1,
|
||||
}
|
||||
```
|
||||
|
||||
## inform结构
|
||||
inform用于客户端主动通知服务端本地事情的发生,比如,自身的某个微服务下线了,就会发送一个inform信息给服务端。
|
||||
|
||||
inform信息会发送给``, 结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"c": "client_id",
|
||||
// 这个inform上传的时间
|
||||
"at": $int64,
|
||||
// 微服务信息
|
||||
"s": [{
|
||||
"n": "微服务名称",
|
||||
"v": "微服务版本",
|
||||
"c": "微服务副本",
|
||||
// 微服务是否在线,0表示离线,1表示在线
|
||||
"s": 0|1
|
||||
}]
|
||||
}
|
||||
```
|
||||
@ -5,6 +5,7 @@
|
||||
{sync, ".*", {git, "https://github.com/rustyio/sync.git", {branch, "master"}}},
|
||||
{cowboy, ".*", {git, "https://github.com/ninenines/cowboy.git", {tag, "2.5.0"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy.git", {tag, "1.1.1"}}},
|
||||
{mysql, ".*", {git, "https://github.com/mysql-otp/mysql-otp", {tag, "1.8.0"}}},
|
||||
{parse_trans, ".*", {git, "https://github.com/uwiger/parse_trans", {tag, "3.0.0"}}},
|
||||
{emqtt, ".*", {git, "https://github.com/emqx/emqtt", {tag, "v1.2.0"}}},
|
||||
{lager, ".*", {git,"https://github.com/erlang-lager/lager.git", {tag, "3.9.2"}}}
|
||||
|
||||
@ -34,6 +34,10 @@
|
||||
0},
|
||||
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1},
|
||||
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},1},
|
||||
{<<"mysql">>,
|
||||
{git,"https://github.com/mysql-otp/mysql-otp",
|
||||
{ref,"caf5ff96c677a8fe0ce6f4082bc036c8fd27dd62"}},
|
||||
0},
|
||||
{<<"parse_trans">>,
|
||||
{git,"https://github.com/uwiger/parse_trans",
|
||||
{ref,"6f3645afb43c7c57d61b54ef59aecab288ce1013"}},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user