简化推送的流程

This commit is contained in:
anlicheng 2025-08-04 16:02:26 +08:00
parent 168508e2e1
commit 6c46e3d92b
7 changed files with 63 additions and 120 deletions

View File

@ -1,9 +1,44 @@
dimension_apn # 基于erlang的推送服务
=====
An OTP application ## mysql
mysql -h127.0.0.1 -uroot -p'r3a-7Qrh#3Q'
Build ## api接口
-----
$ rebar3 compile
### 1. 批量推送消息
url: "/api/push"
method: POST
body:
[
{
"device_token": "b48b911e85874b403ce80cbb33864e8ed6f06455e80310b0f6b95e672a3e39dc",
"title": "动物狂响曲",
"body": "第7集(校服与被毛更深处),bilibili已更新",
"unread_num": 10,
"custom_data": {
"target": "detail",
"params": {
"drama_id": 1234
}
}
},
{
"device_token": "b48b911e85874b403ce80cbb33864e8ed6f06455e80310b0f6b95e672a3e39dc",
"title": "动物狂响曲",
"body": "第7集(校服与被毛更深处),bilibili已更新",
"custom_data": {
"target": "detail",
"params": {
"drama_id": 1234
}
}
}
]
字段说明:
1. custom_data使用指定页面的跳转逻辑target字段目前固定为: detail参数部分的: drama_id指定为要进入的详情页面id

View File

@ -85,16 +85,6 @@ read_body(Req, AccData) ->
%% helper methods %% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% token信息
handle_request("POST", "/api/device_token", _, #{<<"user_id">> := UserId, <<"token">> := Token}) ->
case mnesia_device_token:insert(UserId, Token, dimension_utils:current_time()) of
ok ->
{ok, 200, dimension_utils:json_data(<<"success">>)};
{error, Reason} ->
lager:notice("[api_handler] insert user_id: ~p, token: ~p, error: ~p", [UserId, Token, Reason]),
{ok, 200, dimension_utils:json_error(-1, <<"更新token失败"/utf8>>)}
end;
%% %%
handle_request("POST", "/api/push", _, Notifications) -> handle_request("POST", "/api/push", _, Notifications) ->
{ok, Pid} = dimension_apn_worker:start_link(), {ok, Pid} = dimension_apn_worker:start_link(),

View File

@ -13,7 +13,6 @@
ssl, ssl,
jiffy, jiffy,
apns, apns,
mnesia,
kernel, kernel,
stdlib stdlib
]}, ]},

View File

@ -13,8 +13,6 @@ start(_StartType, _StartArgs) ->
io:setopts([{encoding, unicode}]), io:setopts([{encoding, unicode}]),
%% %%
erlang:system_flag(fullsweep_after, 16), erlang:system_flag(fullsweep_after, 16),
%% mnesia
ok = mnesia:start(),
%% http服务 %% http服务
start_http_server(), start_http_server(),
dimension_apn_sup:start_link(). dimension_apn_sup:start_link().
@ -34,8 +32,7 @@ start_http_server() ->
Dispatcher = cowboy_router:compile([ Dispatcher = cowboy_router:compile([
{'_', [ {'_', [
{"/api/[...]", http_protocol, [api_handler]}, {"/api/[...]", api_handler, []}
{"/ws", ws_channel, []}
]} ]}
]), ]),

View File

@ -32,15 +32,12 @@
test() -> test() ->
{ok, Pid} = start_link(), {ok, Pid} = start_link(),
UserId = <<"9df4dbb1-aff7-4caa-9adb-cb426a7dbcca">>,
DeviceToken = <<"b48b911e85874b403ce80cbb33864e8ed6f06455e80310b0f6b95e672a3e39dc">>, DeviceToken = <<"b48b911e85874b403ce80cbb33864e8ed6f06455e80310b0f6b95e672a3e39dc">>,
Title = <<"动物狂响曲"/utf8>>, Title = <<"动物狂响曲"/utf8>>,
Body = <<"第7集(校服与被毛更深处),bilibili已更新"/utf8>>, Body = <<"第7集(校服与被毛更深处),bilibili已更新"/utf8>>,
mnesia_device_token:insert(UserId, DeviceToken, dimension_utils:current_time()),
push(Pid, [ push(Pid, [
#{ #{
<<"user_id">> => UserId, <<"device_token">> => DeviceToken,
<<"title">> => Title, <<"title">> => Title,
<<"body">> => Body, <<"body">> => Body,
<<"custom_data">> => #{ <<"custom_data">> => #{
@ -107,9 +104,9 @@ handle_call(_Request, _From, State) ->
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
handle_cast({push, Notifications}, State = #state{apns_pid = ApnsPid, headers = Headers}) -> handle_cast({push, Notifications}, State = #state{apns_pid = ApnsPid, headers = Headers}) ->
lists:foreach(fun(#{<<"user_id">> := UserId, <<"title">> := Title, <<"body">> := Body, <<"custom_data">> := CustomData}) -> lists:foreach(fun(#{<<"device_token">> := DeviceToken, <<"title">> := Title, <<"body">> := Body, <<"unread_num">> := UnreadNum, <<"custom_data">> := CustomData}) ->
PushResult = push_task(ApnsPid, UserId, Title, Body, CustomData, Headers), PushResult = push_task(ApnsPid, DeviceToken, Title, Body, UnreadNum, CustomData, Headers),
lager:debug("[dimension_apn_pusher] push result is: ~p", [PushResult]) lager:debug("[dimension_apn_pusher] device_token: ~p, push result is: ~p", [DeviceToken, PushResult])
end, Notifications), end, Notifications),
{noreply, State}; {noreply, State};
@ -147,13 +144,8 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
push_task(ApnsPid, UserId, Title, Body, CustomData, Headers) push_task(ApnsPid, DeviceToken, Title, Body, UnreadNum, CustomData, Headers)
when is_pid(ApnsPid), is_binary(UserId), is_binary(Title), is_binary(Body), is_map(CustomData), is_map(Headers) -> when is_pid(ApnsPid), is_binary(DeviceToken), is_binary(Title), is_binary(Body), is_integer(UnreadNum), is_map(CustomData), is_map(Headers) ->
case mnesia_device_token:get_token(UserId) of
error ->
ok;
{ok, DeviceToken} ->
Notification = #{ Notification = #{
aps => #{ aps => #{
alert => #{ alert => #{
@ -164,13 +156,12 @@ push_task(ApnsPid, UserId, Title, Body, CustomData, Headers)
% %
sound => <<"default">>, sound => <<"default">>,
% App % App
badge => 1 badge => UnreadNum
% category => <<"HUB_MESSAGE">> % category => <<"HUB_MESSAGE">>
}, },
custom_data => CustomData custom_data => CustomData
}, },
apns:push_notification(ApnsPid, DeviceToken, Notification, Headers) apns:push_notification(ApnsPid, DeviceToken, Notification, Headers).
end.
-spec parse_headers(Headers :: list()) -> map(). -spec parse_headers(Headers :: list()) -> map().
parse_headers(Headers) -> parse_headers(Headers) ->

View File

@ -1,27 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author anlicheng
%%% @copyright (C) 2025, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 07. 4 2025 15:47
%%%-------------------------------------------------------------------
-module(dimension_mnesia_manager).
-author("anlicheng").
%% API
-export([init_database/0]).
%%
init_database() ->
%% schema
mnesia:stop(),
mnesia:delete_schema([node()]),
%% schema
ok = mnesia:create_schema([node()]),
ok = mnesia:start(),
%%
mnesia_device_token:create_table(),
ok.

View File

@ -1,42 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author anlicheng
%%% @copyright (C) 2025, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 07. 4 2025 14:33
%%%-------------------------------------------------------------------
-module(mnesia_device_token).
-author("anlicheng").
-include("dimension_tables.hrl").
%% API
-export([create_table/0, insert/3, get_token/1]).
create_table() ->
%% id生成器
mnesia:create_table(device_token, [
{attributes, record_info(fields, device_token)},
{record_name, device_token},
{disc_copies, [node()]},
{type, set}
]).
-spec insert(UserId :: binary(), DeviceToken :: binary(), Timestamp :: integer()) -> ok | {error, Reason :: any()}.
insert(UserId, DeviceToken, Timestamp) when is_binary(UserId), is_binary(DeviceToken), is_integer(Timestamp) ->
Record = #device_token{user_id = UserId, token = DeviceToken, timestamp = Timestamp},
case mnesia:transaction(fun() -> mnesia:write(device_token, Record, write) end) of
{'atomic', Res} ->
Res;
{'aborted', Reason} ->
{error, Reason}
end.
-spec get_token(UserId :: binary()) -> error | {ok, Token :: binary()}.
get_token(UserId) when is_binary(UserId) ->
case mnesia:dirty_read(device_token, UserId) of
[] ->
error;
[#device_token{token = Token} | _] ->
{ok, Token}
end.