ekfa/src/efka_util.erl
2026-04-23 20:44:34 +08:00

143 lines
4.8 KiB
Erlang
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%%%-------------------------------------------------------------------
%%% @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.