143 lines
4.8 KiB
Erlang
143 lines
4.8 KiB
Erlang
%%%-------------------------------------------------------------------
|
||
%%% @author anlicheng
|
||
%%% @copyright (C) 2025, <COMPANY>
|
||
%%% @doc
|
||
%%%
|
||
%%% @end
|
||
%%% Created : 19. 4月 2025 16:33
|
||
%%%-------------------------------------------------------------------
|
||
-module(efka_util).
|
||
-author("anlicheng").
|
||
|
||
%% API
|
||
-export([get_file_md5/1]).
|
||
-export([timestamp/0, number_format/2, timestamp_ms/0, float_to_binary/2, int_format/2]).
|
||
-export([chunks/2, rand_bytes/1, uuid/0, md5/1, sha_uuid/0]).
|
||
-export([json_data/1, json_error/2]).
|
||
-export([starts_with/2, file_md5/1]).
|
||
|
||
-spec get_file_md5(string()) -> string().
|
||
get_file_md5(FilePath) when is_list(FilePath) ->
|
||
{ok, FileData} = file:read_file(FilePath),
|
||
Md5Binary = crypto:hash(md5, FileData),
|
||
string:lowercase(lists:flatten([io_lib:format("~2.16.0b", [X]) || X <- binary_to_list(Md5Binary)])).
|
||
|
||
%% 时间,精确到毫秒
|
||
-spec timestamp_ms() -> integer().
|
||
timestamp_ms() ->
|
||
{Mega, Seconds, Micro} = os:timestamp(),
|
||
(Mega * 1000000 + Seconds) * 1000 + Micro div 1000.
|
||
|
||
-spec timestamp() -> integer().
|
||
timestamp() ->
|
||
{Mega, Seconds, _Micro} = os:timestamp(),
|
||
Mega * 1000000 + Seconds.
|
||
|
||
-spec number_format(integer() | float(), integer()) -> integer() | float().
|
||
number_format(Num, _Decimals) when is_integer(Num) ->
|
||
Num;
|
||
number_format(Float, Decimals) when is_float(Float) ->
|
||
list_to_float(float_to_list(Float, [{decimals, Decimals}, compact])).
|
||
|
||
-spec int_format(integer(), pos_integer()) -> integer().
|
||
int_format(Num, Len) when is_integer(Num), Len > 0 ->
|
||
S = integer_to_list(Num),
|
||
case length(S) > Len of
|
||
true ->
|
||
list_to_integer(lists:sublist(S, 1, Len));
|
||
false ->
|
||
Num
|
||
end.
|
||
|
||
%% 将数据分组
|
||
-spec chunks(list(), integer()) -> [list()].
|
||
chunks(List, Size) when is_list(List), is_integer(Size), Size > 0, length(List) =< Size ->
|
||
[List];
|
||
chunks(List, Size) when is_list(List), is_integer(Size), Size > 0 ->
|
||
chunks0(List, Size, Size, [], []).
|
||
|
||
-spec chunks0(list(), integer(), integer(), list(), [list()]) -> [list()].
|
||
chunks0([], _, _, [], AccTarget) ->
|
||
lists:reverse(AccTarget);
|
||
chunks0([], _, _, Target, AccTarget) ->
|
||
lists:reverse([lists:reverse(Target) | AccTarget]);
|
||
chunks0(List, Size, 0, Target, AccTarget) ->
|
||
chunks0(List, Size, Size, [], [lists:reverse(Target) | AccTarget]);
|
||
chunks0([Hd | Tail], Size, Num, Target, AccTarget) ->
|
||
chunks0(Tail, Size, Num - 1, [Hd | Target], AccTarget).
|
||
|
||
-spec json_data(term()) -> binary().
|
||
json_data(Data) ->
|
||
iolist_to_binary(json:encode(#{
|
||
<<"result">> => Data
|
||
})).
|
||
|
||
-spec json_error(integer(), binary()) -> binary().
|
||
json_error(ErrCode, ErrMessage) when is_integer(ErrCode), is_binary(ErrMessage) ->
|
||
iolist_to_binary(json:encode(#{
|
||
<<"error">> => #{
|
||
<<"code">> => ErrCode,
|
||
<<"message">> => ErrMessage
|
||
}
|
||
})).
|
||
|
||
-spec uuid() -> string().
|
||
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),
|
||
S = lists:flatten([integer_to_list(E, 16) || <<E:4>> <= Bytes]),
|
||
lists:sublist(string:to_lower(S), 1, Size).
|
||
|
||
-spec md5(Str :: binary()) -> binary().
|
||
md5(Str) when is_binary(Str) ->
|
||
list_to_binary(lists:flatten([hex(X) || <<X:4>> <= erlang:md5(Str)])).
|
||
|
||
-spec hex(0..15) -> byte().
|
||
hex(N) when N < 10 ->
|
||
$0 + N;
|
||
hex(N) ->
|
||
$a + (N - 10).
|
||
|
||
-spec float_to_binary(Num :: number(), integer()) -> binary().
|
||
float_to_binary(V, _) when is_integer(V) ->
|
||
integer_to_binary(V);
|
||
float_to_binary(V, Decimals) when is_float(V), is_integer(Decimals) ->
|
||
S = float_to_list(V, [{decimals, Decimals}, compact]),
|
||
list_to_binary(S).
|
||
|
||
%% 生产唯一的uuid
|
||
-spec sha_uuid() -> binary().
|
||
sha_uuid() ->
|
||
Salt = crypto:strong_rand_bytes(32),
|
||
Str = string:lowercase(binary:encode_hex(crypto:hash(sha256, Salt))),
|
||
binary:part(Str, 1, 32).
|
||
|
||
-spec starts_with(Binary :: binary(), Prefix :: binary()) -> boolean().
|
||
starts_with(Binary, Prefix) when is_binary(Binary), is_binary(Prefix) ->
|
||
PrefixSize = byte_size(Prefix),
|
||
case Binary of
|
||
<<Prefix:PrefixSize/binary, _Rest/binary>> -> true;
|
||
_ -> false
|
||
end.
|
||
|
||
-spec file_md5(FilePath :: string()) -> Md5 :: string().
|
||
file_md5(FilePath) when is_list(FilePath) ->
|
||
{ok, F} = file:open(FilePath, [read, binary]),
|
||
Digest = md5_loop(F, crypto:hash_init(md5)),
|
||
file:close(F),
|
||
lists:flatten(io_lib:format("~32.16.0b", [binary:decode_unsigned(Digest)])).
|
||
|
||
-spec md5_loop(file:io_device(), crypto:hash_state()) -> binary().
|
||
md5_loop(F, Context) ->
|
||
%% 每次读取 1MB,可调整块大小
|
||
case file:read(F, 1024 * 1024) of
|
||
eof ->
|
||
crypto:hash_final(Context);
|
||
{ok, Bin} ->
|
||
md5_loop(F, crypto:hash_update(Context, Bin))
|
||
end.
|