我在 apex.oracle.com 上设置了一个演示,但由于您需要对 dbms_alert 执行授权,所以它必须是纯文本的。
整个设置可以走得很远,所以我认为这是构建的基础。
例如,我只处理过一个警报。在您的示例中,您可能希望使用多个事件来捕获不同的进度警报。这是因为为了向客户端返回某些内容(ajax 响应),ajax 回调必须“关闭”。因此,当捕获警报并想要返回它时,您需要写入缓冲区并且需要返回它。这意味着您也将停止收听该事件(阅读:在 apex 中,您应该!)。
考虑这样的流程:您将进行 ajax 调用,并有一个注册对事件感兴趣的 ajax 回调过程。然后等待警报发生。您可以通过将其写入 http 缓冲区 (htp.p) 来捕获并返回它。代码到此结束,apex 将刷新缓冲区,然后 ajax 调用将获取响应,您将能够管理该返回。
不过不要忘记:apex 使用连接池,并且数据库会话不是直接链接的,而是一直重复使用的。您不想“离开”数据库会话“脏”。您也必须取消注册您的警报兴趣。这也为使用唯一 ID 的警报提供了一个案例 - 警报可以注册到不同的(数据库)会话中,所以如果这是一个可供多个用户用来跟踪其进程进度的页面,你不需要希望他们干扰其他用户的警报。
但是,这种转瞬即逝的兴趣也意味着在不同的 ajax 调用之间会有“中断”。当您想要收听多个警报,并且这些警报可能非常紧密地打包在一起时,您可能会错过一个。假设 2 个警报间隔 1 毫秒:第一个将被捕获,报告给 ajax 调用,它必须立即开始一个新调用才能侦听更多警报。但是由于在那段时间内没有活跃的听众,下一个警报可能已经错过了。现在 - 这可能只是您在同一个处理程序下触发多个警报的问题。如果您使用多个处理程序并同时为所有这些处理程序启动 ajax 调用,它们都会得到及时处理。当然,两者都有解决方案。我想,当仅使用一个处理程序时,您可以捕获集合中的所有警报,并检查您是否已经发送了某个警报的响应以及是否继续签入。使用多个处理程序,您可以使用唯一的 id 并为其添加不同的状态后缀。
这是我在本地 POC 中使用的一些实际代码。
概述:我有 3 个按钮:1 个用于生成警报 ID,为此我使用了一个序列。另一个开始监听事件的按钮,以及另一个发送警报的按钮。
NEW_ALERT_ID 按钮的 JS 代码:
apex.server.process("NEW_ALERT").done(function(pdata){
$s("P1_ALERT_ID",pdata.alertId);
})
START_LISTEN 按钮的 JS 代码:
apex.server.process("LISTEN_ALERT",{x01:$v("P1_ALERT_ID")},{timeout:(31*1000)})
.done(function(pdata){
if (pdata.success ){
alert('Caught alert: ' + pdata.message);
} else {
alert("No alerts caught during wait on database. You may want to continue listening in...")
}
})
.fail(function(jqXHR, textStatus){
if(textStatus === 'timeout')
{
alert('Call should have returned by now...');
//do something. Try again perhaps?
}
});
SEND_ALERT 按钮的 JS 代码:
apex.server.process("SEND_ALERT",{x01:$v("P1_ALERT_ID")},{dataType:"text"});
AJAX 回调进程:
NEW_ALERT:
htp.p('{"alertId":'||alert_seq.nextval()||'}');
LISTEN_ALERT:
declare
alert_id number := apex_application.g_x01;
msg varchar2(2000);
stat pls_integer;
keep_looping boolean := true;
insurance binary_integer := 0; -- prevent an infinite loop
onecycle binary_integer := 3; -- one cycle of waiting, in seconds
maxcycles binary_integer := 10; -- in this session, the max amount of cycles to wait
begin
dbms_alert.register(alert_id);
while keep_looping
loop
insurance := insurance + 1;
dbms_alert.waitone(alert_id, msg, stat, onecycle);
if stat = 1 then
apex_debug.message('timeout occured, going again');
else
apex_debug.message('alert: '||msg);
keep_looping := false;
end if;
exit when insurance = maxcycles;
end loop;
if keep_looping then
-- we waited a really long time now. It may be a good idea to return this info to the client and let it start a new call
htp.p('{"success":false,"message":"No alert during wait on database"}');
else
htp.p('{"success":true,"message":"'||msg||'"}');
end if;
end;
SEND_ALERT:
declare
alert_id number := apex_application.g_x01;
begin
dbms_alert.signal(alert_id, 'alert sent at '||to_char(systimestamp, 'HH24:MI:SS FF6'));
end;
所以,我首先会获得一个警报 ID,然后我会开始收听,然后在某个时候我会发送一个警报(或不发送)。虽然它是一个骨架,但需要在您的实际设置中进一步完善。