feat: add dlink auth

This commit is contained in:
U-JOHNLIU\jonhl 2022-01-24 17:47:17 +08:00
parent 5a92781954
commit 3729770a5e
13 changed files with 190 additions and 132 deletions

View File

@ -8,10 +8,10 @@ parse.delete_field = ACL,objectId,updatedAt,createdAt
##--------------------------------------------------------------------
parse.parse_server = http://prod.iotn2n.com:1337
parse.parse_path = /parse/
parse.parse_appid = 49aa5a778d7ad75444d6ec010df4c633
parse.parse_master_key = 44f4052cae56b51e097420d23844a6ef
parse.parse_js_key = 7f2cf7f9ee16fabfe48cc48feed72347
parse.parse_rest_key = 35f51e718318f432a41e8bd9453f0c03
parse.parse_appid = d3300b6f53d7ee7da766142f2f7050eb
parse.parse_master_key = 3d9707db9414c4e398c5036ddb1b2b62
parse.parse_js_key = fc8f19e9dcc4b8b7848aed1f8dcda317
parse.parse_rest_key = 7957b8a5923c8be145b317521b922b18
##--------------------------------------------------------------------
## parse cache

View File

@ -45,18 +45,16 @@ load_device(Order) ->
post(Device) ->
DeviceId = maps:get(<<"objectId">>, Device),
DeviceName = maps:get(<<"name">>, Device),
Devaddr = maps:get(<<"devaddr">>, Device),
Product = maps:get(<<"product">>, Device),
ProductId = maps:get(<<"objectId">>, Product),
<<DeviceSecretdefult:10/binary, _/binary>> = dgiot_utils:to_md5(dgiot_utils:random()),
DeviceSecret = maps:get(<<"deviceSecret">>, Device, DeviceSecretdefult),
DeviceSecret = maps:get(<<"deviceSecret">>, Device, <<"DeviceSecretdefault">>),
Status =
case maps:get(<<"status">>, Device, <<"OFFLINE">>) of
<<"OFFLINE">> -> false;
_ -> true
end,
dgiot_mnesia:insert(DeviceId, {[Status, dgiot_datetime:now_secs(), get_acl(Device), DeviceName, Devaddr, ProductId, DeviceSecret], node()}).
dgiot_mnesia:insert(DeviceId, {[Status, dgiot_datetime:now_secs(), get_acl(Device), Devaddr, ProductId, DeviceSecret], node()}).
put(Device) ->
DeviceId = maps:get(<<"objectId">>, Device),
@ -75,12 +73,11 @@ put(Device) ->
save(Device) ->
DeviceId = maps:get(<<"objectId">>, Device),
DeviceName = maps:get(<<"name">>, Device, <<"">>),
Devaddr = maps:get(<<"devaddr">>, Device),
Product = maps:get(<<"product">>, Device),
ProductId = maps:get(<<"objectId">>, Product),
<<DeviceSecretdefult:10/binary, _/binary>> = dgiot_utils:to_md5(dgiot_utils:random()),
DeviceSecret = maps:get(<<"deviceSecret">>, Device, DeviceSecretdefult),
DeviceSecret = maps:get(<<"devicesecret">>, Device, DeviceSecretdefult),
UpdatedAt =
case maps:get(<<"updatedAt">>, Device, dgiot_datetime:now_secs()) of
<<Data:10/binary, "T", Time:8/binary, _/binary>> ->
@ -92,7 +89,7 @@ save(Device) ->
<<"OFFLINE">> -> false;
_ -> true
end,
dgiot_mnesia:insert(DeviceId, {[Status, UpdatedAt, get_acl(Device), DeviceName, Devaddr, ProductId, DeviceSecret], node()}).
dgiot_mnesia:insert(DeviceId, {[Status, UpdatedAt, get_acl(Device), Devaddr, ProductId, DeviceSecret], node()}).
get_acl(Device) when is_map(Device) ->
ACL = maps:get(<<"ACL">>, Device, #{}),
@ -163,7 +160,7 @@ sync_parse(OffLine) ->
{_, DeviceId, V} = X,
Now = dgiot_datetime:now_secs(),
case V of
{[_, Last, Acl, DeviceName, Devaddr, ProductId, DeviceSecret], Node} when (Now - Last) < 0 ->
{[_, Last, Acl, Devaddr, ProductId, DeviceSecret], Node} when (Now - Last) < 0 ->
case dgiot_parse:update_object(<<"Device">>, DeviceId, #{<<"status">> => <<"ONLINE">>}) of
{ok, _R} ->
Productname =
@ -173,13 +170,13 @@ sync_parse(OffLine) ->
_ ->
<<"">>
end,
?MLOG(info, #{<<"deviceid">> => DeviceId, <<"devaddr">> => Devaddr, <<"productid">> => ProductId, <<"productname">> => Productname, <<"devicename">> => DeviceName, <<"status">> => <<"上线"/utf8>>}, ['device_statuslog']),
dgiot_mnesia:insert(DeviceId, {[true, Now, Acl, DeviceName, Devaddr, ProductId, DeviceSecret], Node});
?MLOG(info, #{<<"deviceid">> => DeviceId, <<"devaddr">> => Devaddr, <<"productid">> => ProductId, <<"productname">> => Productname, <<"status">> => <<"上线"/utf8>>}, ['device_statuslog']),
dgiot_mnesia:insert(DeviceId, {[true, Now, Acl, Devaddr, ProductId, DeviceSecret], Node});
_ ->
pass
end,
timer:sleep(50);
{[true, Last, Acl, DeviceName, Devaddr, ProductId, DeviceSecret], Node} when (Now - Last) > OffLine ->
{[true, Last, Acl, Devaddr, ProductId, DeviceSecret], Node} when (Now - Last) > OffLine ->
case dgiot_parse:update_object(<<"Device">>, DeviceId, #{<<"status">> => <<"OFFLINE">>}) of
{ok, _R} ->
Productname =
@ -189,14 +186,14 @@ sync_parse(OffLine) ->
_ ->
<<"">>
end,
?MLOG(info, #{<<"deviceid">> => DeviceId, <<"devaddr">> => Devaddr, <<"productid">> => ProductId, <<"productname">> => Productname, <<"devicename">> => DeviceName, <<"status">> => <<"下线"/utf8>>}, ['device_statuslog']),
?MLOG(info, #{<<"deviceid">> => DeviceId, <<"devaddr">> => Devaddr, <<"productid">> => ProductId, <<"productname">> => Productname, <<"status">> => <<"下线"/utf8>>}, ['device_statuslog']),
dgiot_umeng:save_devicestatus(DeviceId, <<"OFFLINE">>),
dgiot_mnesia:insert(DeviceId, {[false, Last, Acl, DeviceName, Devaddr, ProductId, DeviceSecret], Node});
dgiot_mnesia:insert(DeviceId, {[false, Last, Acl, Devaddr, ProductId, DeviceSecret], Node});
_ ->
pass
end,
timer:sleep(50);
{[false, Last, Acl, DeviceName, Devaddr, ProductId, DeviceSecret], Node} when (Now - Last) < OffLine ->
{[false, Last, Acl, Devaddr, ProductId, DeviceSecret], Node} when (Now - Last) < OffLine ->
case dgiot_parse:update_object(<<"Device">>, DeviceId, #{<<"status">> => <<"ONLINE">>}) of
{ok, _R} ->
Productname =
@ -206,8 +203,8 @@ sync_parse(OffLine) ->
_ ->
<<"">>
end,
?MLOG(info, #{<<"deviceid">> => DeviceId, <<"devaddr">> => Devaddr, <<"productid">> => ProductId, <<"productname">> => Productname, <<"devicename">> => DeviceName, <<"status">> => <<"上线"/utf8>>}, ['device_statuslog']),
dgiot_mnesia:insert(DeviceId, {[true, Last, Acl, DeviceName, Devaddr, ProductId, DeviceSecret], Node});
?MLOG(info, #{<<"deviceid">> => DeviceId, <<"devaddr">> => Devaddr, <<"productid">> => ProductId, <<"productname">> => Productname, <<"status">> => <<"上线"/utf8>>}, ['device_statuslog']),
dgiot_mnesia:insert(DeviceId, {[true, Last, Acl, Devaddr, ProductId, DeviceSecret], Node});
_ ->
pass
end,

View File

@ -109,7 +109,7 @@ init(?TYPE, ChannelId, Args) ->
{ok, State, []}.
handle_init(State) ->
erlang:send_after(300, self(), {message, <<"_Pool">>, load}),
erlang:send_after(500, self(), {message, <<"_Pool">>, load}),
erlang:send_after(3 * 60 * 1000, self(), {message, <<"_Pool">>, check}),
{ok, State}.

View File

@ -26,3 +26,13 @@
## 室内定位
通室内定位设备定位,需要专用的定位网关和设备,定位精度可以到米级,投资巨大
## 离线地图
https://github.com/dxxzst/OfflineMap
### 地图瓦片下载工具及配置
+ 工具下载地址https://pan.baidu.com/s/1miMF9nM
+ 工具开源地址https://github.com/luxiaoxun/MapDownloader

View File

@ -8,22 +8,19 @@
+ 设备侧topic交互采用 {productId}/{deviceAddr}的组合来唯一标识设备, deviceAddr为设备物理地址
+ 用户侧topic交互采用{deviceId}来唯一标识设备,用{userId}来唯一标识用户,deviceId为设备虚拟地址
### 鉴权设计
+ deviceId=md5("Device" + {productId} + {devAddr}).subString(10)
+ %u 表示用Username做ACL规则
+ %c 表示用ClientId做ACL规则
+ %d 表示用DeviceAddr做ACL规则, Token是dgiot用户登录权限系统的token,与API权限一致
+ %t 表示用Token做ACL规则, Token是dgiot用户登录权限系统的token,与API权限一致
+ %c 表示用clientId做ACL规则
- 用户侧clientId用Token做ACL规则, Token是dgiot用户登录权限系统的token,与API权限一致
- 设备侧clientId可用deviceAddr或者deviceId,如果用deviceAddr需要用户自己保证唯一性
| 客户端 | Username | Password | ClientId | 登录鉴权| 订阅ACL | 发布ACL|
| -------- | -------- | ------- | -------- |-------- | ------- | -------- |
| Device |{productId}|{productSecret}|{deviceId}| 一型一密 | $dg/device/%u/# | $dg/thing/%u/# |
| Device |{productId}|{deviceSecret}|{deviceId}| 一机一密 | $dg/device/%u/%d/# | $dg/thing/%u/%d/# |
| Device |{productId}|{productSecret}|{deviceId}| 证书加密 | $dg/device/%u/# | $dg/thing/%u/# |
| User |{userId}|{Token}|{Token}| Token认证 | $dg/user/%t/# | $dg/thing/%t/# |
| Device |{productId}|{productSecret}|{clientId}| 一型一密 | $dg/device/%u/# | $dg/thing/%u/# |
| Device |{productId}|{deviceSecret}|{clientId}| 一机一密 | $dg/device/%u/%d/# | $dg/thing/%u/%c/# |
| Device |{productId}|{productSecret}|{clientId}| 证书加密 | $dg/device/%u/# | $dg/thing/%u/# |
| User |{userId}|{Token}|{Token}| Token认证 | $dg/user/%c/# | $dg/thing/%c/# |
## topic设计
| 分类 | Topic | 发布者 | 订阅者 |

View File

@ -20,56 +20,88 @@
%% ACL Callbacks
-export([
check_acl/5
, description/0
]).
check_acl/5
, description/0
]).
check_acl(ClientInfo = #{ clientid := _Clientid }, PubSub, Topic, _NoMatchAction, _Params) ->
check_acl(ClientInfo, PubSub, <<"$dg/", _/binary>> = Topic, _NoMatchAction, _Params) ->
io:format("~s ~p Topic: ~p _NoMatchAction ~p ~n", [?FILE, ?LINE, Topic, _NoMatchAction]),
_Username = maps:get(username, ClientInfo, undefined),
Acls = [],
case match(ClientInfo, PubSub, Topic, Acls) of
case do_check(ClientInfo, PubSub, Topic) of
allow ->
ok;
%% {stop, allow};
{stop, allow};
deny ->
%% {stop, deny};
{stop, allow};
{stop, deny};
_ ->
ok
end.
end;
description() -> "Acl with Mnesia".
check_acl(_ClientInfo, _PubSub, _Topic, _NoMatchAction, _Params) ->
ok.
description() -> "Acl with Dlink".
%%--------------------------------------------------------------------
%% Internal functions
%%-------------------------------------------------------------------
match(_ClientInfo, _PubSub, _Topic, []) ->
nomatch;
match(ClientInfo, PubSub, Topic, [ {_, ACLTopic, Action, Access, _} | Acls]) ->
case match_actions(PubSub, Action) andalso match_topic(ClientInfo, Topic, ACLTopic) of
true -> Access;
false -> match(ClientInfo, PubSub, Topic, Acls)
%% "$dg/user/deviceid/#"
do_check(#{clientid := ClientID, username := Username} = _ClientInfo, subscribe, <<"$dg/user/", DeviceInfo/binary>> = Topic)
when ClientID =/= undefined ->
io:format("~s ~p Topic: ~p~n", [?FILE, ?LINE, Topic]),
[DeviceID | _] = binary:split(DeviceInfo, <<"/">>),
%% ClientID为 Token
case check_device_acl(ClientID, DeviceID, Username) of
ok ->
allow;
_ ->
deny
end;
%%"$dg/device/productid/devaddr/#"
do_check(#{clientid := ClientID} = _ClientInfo, subscribe, <<"$dg/device/", DeviceInfo/binary>> = Topic) ->
io:format("~s ~p Topic: ~p~n", [?FILE, ?LINE, Topic]),
[ProuctID, Devaddr | _] = binary:split(DeviceInfo, <<"/">>, [global]),
DeviceID = dgiot_parse:get_deviceid(ProuctID, Devaddr),
case ClientID == DeviceID of
true ->
allow;
_ ->
deny
end;
%%"$dg/thing/deviceid/#"
%%"$dg/thing/productid/devaddr/#"
do_check(#{clientid := ClientID, username := UserId} = _ClientInfo, publish, <<"$dg/thing/", DeviceInfo/binary>> = Topic)
when ClientID =/= undefined ->
io:format("~s ~p Topic: ~p~n", [?FILE, ?LINE, Topic]),
[Id, Devaddr | _] = binary:split(DeviceInfo, <<"/">>, [global]),
%% clientid为Token
case check_device_acl(ClientID, Id, UserId) of
ok ->
allow;
_ ->
DeviceID = dgiot_parse:get_deviceid(Id, Devaddr),
case ClientID == DeviceID of
true ->
allow;
_ ->
deny
end
end;
do_check(_ClientInfo, _PubSub, Topic) ->
io:format("~s ~p Topic: ~p~n", [?FILE, ?LINE, Topic]),
deny.
check_device_acl(Token, DeviceID, UserId) ->
case dgiot_auth:get_session(Token) of
#{<<"objectId">> := UserId, <<"ACL">> := Acl} ->
case dgiot_device:get_acl(DeviceID) of
Acl ->
ok;
_ ->
deny
end;
_ -> deny
end.
match_topic(ClientInfo, Topic, ACLTopic) when is_binary(Topic) ->
emqx_topic:match(Topic, feed_var(ClientInfo, ACLTopic)).
match_actions(subscribe, sub) -> true;
match_actions(publish, pub) -> true;
match_actions(_, _) -> false.
feed_var(ClientInfo, Pattern) ->
feed_var(ClientInfo, emqx_topic:words(Pattern), []).
feed_var(_ClientInfo, [], Acc) ->
emqx_topic:join(lists:reverse(Acc));
feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) ->
feed_var(ClientInfo, Words, [<<"%c">>|Acc]);
feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) ->
feed_var(ClientInfo, Words, [ClientId |Acc]);
feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) ->
feed_var(ClientInfo, Words, [<<"%u">>|Acc]);
feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) ->
feed_var(ClientInfo, Words, [Username|Acc]);
feed_var(ClientInfo, [W|Words], Acc) ->
feed_var(ClientInfo, Words, [W|Acc]).

View File

@ -22,11 +22,13 @@
%%
%% @end
-module(dgiot_mqtt_app).
-emqx_plugin(?MODULE).
-emqx_plugin(auth).
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).
-export([start/2,
prep_stop/1,
stop/1]).
%% ===================================================================
@ -35,8 +37,21 @@
start(_StartType, _StartArgs) ->
{ok, Sup} = dgiot_mqtt_sup:start_link(),
_ = load_auth_hook(),
_ = load_acl_hook(),
{ok, Sup}.
stop(_State) ->
ok.
prep_stop(State) ->
emqx:unhook('client.authenticate', fun dgiot_mqtt_auth:check/3),
emqx:unhook('client.check_acl', fun dgiot_mqtt_acl:check_acl/5),
State.
load_auth_hook() ->
emqx:hook('client.authenticate', fun dgiot_mqtt_auth:check/3, [#{hash_type => plain}]).
load_acl_hook() ->
emqx:hook('client.check_acl', fun dgiot_mqtt_acl:check_acl/5, [#{}]).

View File

@ -31,50 +31,47 @@
, description/0
]).
check(#{username := Username}, AuthResult, _)
when Username == <<"anonymous">> orelse Username == undefined orelse Username == <<>> ->
io:format("~s ~p Username: ~p~n", [?FILE, ?LINE, Username]),
{stop, AuthResult#{anonymous => true, auth_result => success}};
check(ClientInfo = #{clientid := _Clientid,
password := NPassword}, AuthResult, #{hash_type := HashType}) ->
_Username = maps:get(username, ClientInfo, undefined),
List = [],
case match_password(NPassword, HashType, List) of
false ->
%% ?LOG(error, "[Mnesia] Auth from mnesia failed: ~p", [ClientInfo]),
{stop, AuthResult#{anonymous => false, auth_result => password_error}};
%% clientid password token
check(#{clientid := Token, username := UserId, password := Token}, AuthResult, #{hash_type := _HashType}) ->
io:format("~s ~p UserId: ~p~n", [?FILE, ?LINE, UserId]),
case dgiot_auth:get_session(Token) of
#{<<"objectId">> := UserId} ->
{stop, AuthResult#{anonymous => false, auth_result => success}};
_ ->
{stop, AuthResult#{anonymous => false, auth_result => success}}
end.
{stop, AuthResult#{anonymous => false, auth_result => password_error}}
end;
%% ClientID deviceID deviceAddr Username ProductID
%% 1 11
%% 2 ClientID deviceID的1机1密认证
%% 3 ClientID deviceAddr的1机1密认证
check(#{clientid := ClientId, username := ProductID, password := Password}, AuthResult, #{hash_type := _HashType}) ->
io:format("~s ~p ProductID: ~p ClientId ~p Password ~p ~n", [?FILE, ?LINE, ProductID, ClientId, Password]),
case dgiot_product:lookup_prod(ProductID) of
{ok, #{<<"productSecret">> := Password}} ->
{stop, AuthResult#{anonymous => false, auth_result => success}};
_ ->
case dgiot_device:lookup(ClientId) of
{ok, #{<<"devicesecret">> := Password}} ->
{stop, AuthResult#{anonymous => false, auth_result => success}};
_ ->
DeviceID = dgiot_parse:get_deviceid(ProductID, ClientId),
case dgiot_device:lookup(DeviceID) of
{ok, #{<<"devicesecret">> := Password, <<"devaddr">> := ClientId}} ->
{stop, AuthResult#{anonymous => false, auth_result => success}};
_ ->
{stop, AuthResult#{anonymous => false, auth_result => password_error}}
end
end
end;
check(#{username := Username}, AuthResult, _) ->
io:format("~s ~p Username: ~p~n", [?FILE, ?LINE, Username]),
{stop, AuthResult#{anonymous => false, auth_result => password_error}}.
description() -> "Authentication with Mnesia".
match_password(Password, HashType, HashList) ->
lists:any(
fun(Secret) ->
case is_salt_hash(Secret, HashType) of
true ->
<<Salt:4/binary, Hash/binary>> = Secret,
Hash =:= hash(Password, Salt, HashType);
_ ->
Secret =:= hash(Password, HashType)
end
end, HashList).
hash(undefined, HashType) ->
hash(<<>>, HashType);
hash(Password, HashType) ->
emqx_passwd:hash(HashType, Password).
hash(undefined, SaltBin, HashType) ->
hash(<<>>, SaltBin, HashType);
hash(Password, SaltBin, HashType) ->
emqx_passwd:hash(HashType, <<SaltBin/binary, Password/binary>>).
is_salt_hash(_, plain) ->
true;
is_salt_hash(Secret, HashType) ->
not (byte_size(Secret) == len(HashType)).
len(md5) -> 32;
len(sha) -> 40;
len(sha256) -> 64;
len(sha512) -> 128.

View File

@ -76,8 +76,6 @@ start(ChannelId, ChannelArgs) ->
init(?TYPE, ChannelId, #{
<<"product">> := Products,
<<"auth">> := Auth}) ->
%% load_auth_hook(),
%% load_acl_hook(),
%% io:format("Products = ~p.~n", [Products]),
lists:map(fun(X) ->
case X of
@ -104,7 +102,6 @@ init(?TYPE, ChannelId, #{
},
dgiot_rule_handler:sysc_rules(),
emqx_rule_engine_api:list_rules(#{}, []),
%% dgiot_matlab_tcp:start(Port, State)
{ok, State};
init(?TYPE, _ChannelId, _Args) ->
@ -273,10 +270,3 @@ create_rules(RuleID, ChannelId, Description, Rawsql, Target_topic) ->
_ -> pass
end
end.
%%load_auth_hook() ->
%% emqx:hook('client.authenticate', fun dgiot_mqtt_auth:check/3, [#{hash_type => plain}]).
%%
%%load_acl_hook() ->
%% emqx:hook('client.check_acl', fun dgiot_mqtt_acl:check_acl/5, [#{}]).

View File

@ -8,10 +8,10 @@ parse.delete_field = ACL,objectId,updatedAt,createdAt
##--------------------------------------------------------------------
parse.parse_server = http://prod.iotn2n.com:1337
parse.parse_path = /parse/
parse.parse_appid = 4def2774e6404456e17f8f58295f5f09
parse.parse_master_key = 4d5d2aa27bc82eca4797a0ab73e85f43
parse.parse_js_key = 13d64975825ea872ea2f724887861edd
parse.parse_rest_key = a216e04802ca54f6e709e7fba1b2965b
parse.parse_appid = d3300b6f53d7ee7da766142f2f7050eb
parse.parse_master_key = 3d9707db9414c4e398c5036ddb1b2b62
parse.parse_js_key = fc8f19e9dcc4b8b7848aed1f8dcda317
parse.parse_rest_key = 7957b8a5923c8be145b317521b922b18
##--------------------------------------------------------------------
## parse cache

BIN
changelog-from-release Normal file

Binary file not shown.

View File

@ -1,9 +1,7 @@
#!/bin/bash
# This file is used to install dgiot on linux systems. The operating system
# is required to use systemd to manage services at boot
export PATH=$PATH:/usr/local/bin
lanip=""
wlanip=""
serverFqdn=""

22
logfile Normal file
View File

@ -0,0 +1,22 @@
2022-01-18 13:53:46.308 HKT [155528] 日志: listening on IPv6 address "::1", port 5432
2022-01-18 13:53:46.308 HKT [155528] 日志: listening on IPv4 address "127.0.0.1", port 5432
2022-01-18 13:53:46.521 HKT [155604] 日志: 数据库上次关闭时间为 2022-01-18 13:53:43 HKT
2022-01-18 13:53:46.837 HKT [155528] 日志: 数据库系统准备接受连接
2022-01-18 13:59:39.067 HKT [155560] 致命错误: 角色 "postgres" 不存在
2022-01-18 13:59:39.103 HKT [152052] 致命错误: 角色 "postgres" 不存在
2022-01-18 13:59:57.737 HKT [151300] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:06:45.947 HKT [151988] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:06:45.973 HKT [146740] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:07:01.365 HKT [155248] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:10:00.274 HKT [132012] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:10:00.305 HKT [156660] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:10:16.933 HKT [104700] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:40:29.469 HKT [159448] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:40:29.520 HKT [153628] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:40:34.014 HKT [157792] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:40:46.823 HKT [157024] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:40:46.885 HKT [156156] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:40:49.583 HKT [151236] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:55:02.274 HKT [158952] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:55:02.327 HKT [150864] 致命错误: 角色 "postgres" 不存在
2022-01-18 14:55:11.170 HKT [153544] 致命错误: 角色 "postgres" 不存在