%%%------------------------------------------------------------------- %%% @author anlicheng %%% @copyright (C) 2025, %%% @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) || <> <= 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) || <> <= 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 <> -> 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.