简化推送的流程
This commit is contained in:
parent
168508e2e1
commit
6c46e3d92b
47
README.md
47
README.md
@ -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
|
||||||
@ -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(),
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
ssl,
|
ssl,
|
||||||
jiffy,
|
jiffy,
|
||||||
apns,
|
apns,
|
||||||
mnesia,
|
|
||||||
kernel,
|
kernel,
|
||||||
stdlib
|
stdlib
|
||||||
]},
|
]},
|
||||||
|
|||||||
@ -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, []}
|
|
||||||
]}
|
]}
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|||||||
@ -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) ->
|
||||||
|
|||||||
@ -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.
|
|
||||||
@ -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.
|
|
||||||
Loading…
x
Reference in New Issue
Block a user