20 通知最终用户#

Oracle APEX 应用可以通过三类方式通知最终用户:页面上的成功与错误消息、电子邮件,以及推送通知。这些通知既可以通过声明式属性完成,也可以在 PL/SQL 或 JavaScript 代码中触发。

学习本章时,可以把通知分成两个问题:首先,用户当前正在页面上操作时,如何立即告诉他操作结果;其次,用户不在当前页面时,如何通过邮件或设备通知把消息送达。

20.1 显示成功与错误消息#

成功消息和错误消息用于反馈操作是否完成。成功消息通常告诉用户保存、提交、处理等动作已经成功;错误消息则应说明失败原因,并尽量给出可修正的方向。

APEX 支持在页面进程、验证、动态操作、服务器端代码和客户端 JavaScript 中显示消息。对于数据库约束、触发器异常或 APEX 引擎错误,还可以配置错误处理函数(Error Handling Function),在错误显示给用户前改写消息内容。

20.1.1 配置成功与错误消息#

页面进程可以在属性中直接配置成功消息(Success Message)和错误消息(Error Message)。最简单的方式是在属性里写固定文本,也可以使用页面项替换语法,让消息带上当前页面数据。

Your order number is &P113_ORDER_ID. and will ship &P113_SHIP_DATE.

如果应用需要多语言消息,建议把消息定义为可翻译文本消息(Text Message),再在页面进程中引用。下面的写法引用名为 ORDER_CONFIRMATION 的文本消息,并传入 ordnumships 两个占位符。

&{ORDER_CONFIRMATION ordnum="&P113_ORDER_ID." ships="&P113_SHIP_DATE."}.

当文本消息本身包含 HTML 标记时,使用 !RAW 修饰符可以避免显示时被转义。

&{ORDER_CONFIRMATION ordnum="&P113_ORDER_ID." ships="&P113_SHIP_DATE."}!RAW.

如果一次提交中有多个页面进程都产生成功消息,APEX 会把这些成功消息合并显示。

图 20-1 多个成功消息会合并显示。

成功消息可以包含简单 HTML。例如可以用换行和加粗标记控制显示格式。

<br>Success <strong>Message</strong> 2
图 20-2 成功消息可以包含 HTML 标记。

错误消息的行为不同。多个页面进程的错误消息不会继续累积;第一个失败的进程会停止后续处理,并显示该错误。

图 20-3 第一个页面进程错误会中止处理并显示消息。

如果消息内容需要先由代码计算,可以在前置页面进程中把结果写入隐藏页面项,例如 P113_COMPUTED_MESSAGE,再在同一进程或后续进程的消息属性中通过 &P113_COMPUTED_MESSAGE. 引用。

20.1.2 使用验证一次显示多个错误#

APEX 验证(Validation)适合处理用户输入规则。提交页面时,所有失败的验证都会一起显示,用户可以一次看到需要修正的全部问题。

字段相关的验证可以关联到具体页面项,并显示在字段附近;没有绑定字段的验证则显示在页面通知区域。这样既能提供全局错误列表,也能把具体错误放在用户需要修改的位置。

图 20-4 提交时显示所有失败的验证错误。

20.1.2.1 通过返回 True/False 做验证#

类型为 PL/SQL Expression 的验证会把表达式结果作为通过与否的判断:表达式返回 true 时数据有效;返回 false 时显示验证的 Error Message。

例如,验证“两个字段不能都以 4 开头”可以写成下面的表达式。它的含义是:不是两个页面项的首字符都等于 4

not (
  substr(:P3_MULTIPLE_OF_TEN,1,1) = '4'
  and substr(:P3_ODD_MULTIPLE_OF_FIVE,1,1) = '4'
)

等价地,也可以表达为“至少有一个字段首字符不是 4”。

substr(:P3_MULTIPLE_OF_TEN,1,1) != '4'
or substr(:P3_ODD_MULTIPLE_OF_FIVE,1,1) != '4'

为了支持翻译,可以定义名为 CANNOT_BOTH_START 的文本消息,并使用 first_digit 占位符。

Cannot both start with a %first_digit

验证的错误消息属性可以这样引用它:

&{CANNOT_BOTH_START first_digit="4"}.
图 20-5 表达式返回 false 时显示配置的错误消息。

20.1.2.2 通过返回错误文本做验证#

类型为 Function Body (returning Error Text) 的验证使用返回值表达结果:返回 null 表示通过;返回其他文本表示失败,且该文本就是要显示的错误消息。

下面示例检查两个数字字段是否至少有一个大于 100。如果都小于 100,则通过 APEX_LANG.GET_MESSAGE 返回可翻译错误消息。

return case
  when :P3_MULTIPLE_OF_TEN < 100
   and :P3_ODD_MULTIPLE_OF_FIVE < 100 then
    apex_lang.get_message(
      'AT_LEAST_N_ABOVE_M',
      apex_t_varchar2(
        'mincount','1',
        'limit','100'))
end;

这里的 CASE 没有 ELSE 分支,因此条件不满足时自然返回 null,验证通过。

图 20-6 验证通过返回错误文本来表示失败。

20.1.3 自动关闭成功消息#

默认情况下,成功消息和错误消息会一直显示,直到用户关闭。也可以在应用的用户界面属性中启用 Auto Dismiss Success Messages,让成功消息在五秒后自动消失。

这个选项需要谨慎使用。自动消失可能影响可访问性,有些用户来不及读完消息。如果启用,最好配合 apex.message.setDismissPreferences JavaScript API,让用户可以选择是否自动关闭。

图 20-7 启用五秒后自动关闭成功消息。

20.1.4 使用动态操作显示消息#

动态操作(Dynamic Action)可以在页面事件发生时显示成功消息或错误消息。原生动作 Show Success MessageShow Error Message 适合在不提交页面的情况下向用户反馈。

下面的示例在用户修改员工编号后立即做服务器端检查。包 MESSAGES_APP 中的 CHECK_EMPNO 函数如果找到合法员工编号就返回 null,否则返回错误消息。

-- In package messages_app
function check_empno(
  p_empno in number)
  return varchar2
is
begin
  for j in (select null from emp where empno = p_empno) loop
    return null; -- valid
  end loop;
  return apex_lang.get_message('INVALID_EMPNO'); -- invalid
end;

页面上准备一个隐藏页面项 P4_INVALID_MESSAGE,并关闭 Value Protected。动态操作 Eagerly Check Empno 使用 Execute Server-side Code 执行下面一行 PL/SQL,把检查结果写入隐藏项。

:P4_INVALID_MESSAGE := messages_app.check_empno(:P4_EMPNO);

该动作需要在 Items to Submit 中提交 P4_EMPNO,并在 Items to Return 中返回 P4_INVALID_MESSAGE,确保浏览器中的最新值传到服务器,服务器结果再回到页面。

图 20-8 动态操作即时调用 CHECK_EMPNO。

接着添加 Show Error Message 动作,仅当 P4_INVALID_MESSAGE 不为空时执行。消息文本可以使用 &P4_INVALID_MESSAGE. 替换语法。

图 20-9 有服务器端错误结果时才显示页面级错误。

同一检查还应在提交时通过正式验证执行,避免只依赖客户端交互。示例中的 Check Valid Empno 验证使用 Function Body (returning Error Text) 类型调用同一个函数。

图 20-10 提交页面时执行相同检查。

最终效果是用户离开 Employee ID 字段后立即看到无效员工编号的页面级错误,不必等到提交页面。

图 20-11 提交前即时显示页面级错误消息。

20.1.5 通过程序显示消息#

除了声明式属性,APEX 还提供服务器端和客户端 API。服务器端可以用 APEX_ERROR.ADD_ERROR 添加错误;客户端可以用 apex.message 显示或清除成功与错误消息。

20.1.5.1 在 PL/SQL 中添加错误消息#

APEX_ERROR.ADD_ERROR 用于在服务器端处理中向用户报告错误。它会停止后续处理,放弃未提交的数据变更,并在通知区域显示错误。

这种方式特别适合放在保存数据之后、APEX 最终提交之前的页面进程中,用来检查跨行、聚合或业务级规则。

20.1.5.1.1 利用读一致性执行规则检查

用户提交页面后,页面进程会先把变更写入数据库,但 APEX 引擎会等所有提交处理都成功后才提交事务。只要某个后续页面进程报告错误,就能否决整次提交,之前保存进程写入的变更也不会提交。

Oracle 的读一致性让后续 SQL 能看见当前事务里尚未提交的变更,因此可以在保存进程之后运行聚合查询,例如 SUMCOUNT,用来判断即将提交的数据是否违反业务规则。

20.1.5.1.2 用 SQL 聚合实现约束

假设业务规则要求每个部门的月度总薪酬不能超过 12000。下面的查询用 SUMGROUP BYHAVING 找出超限部门。COALESCE 把空工资或佣金按 0 处理。

-- Return any departments over monthly compensation limit
select deptno,
       sum(coalesce(sal,0) + coalesce(comm,0)) as total_comp
from emp
group by deptno
having sum(coalesce(sal,0) + coalesce(comm,0)) > 12000

过程 CHECK_DEPARTMENTS_OVERBUDGET 遍历这些部门,把部门号和总薪酬加入字符串列表。如果列表不为空,就调用 APEX_ERROR.ADD_ERROR,显示通过 APEX_LANG.GET_MESSAGE 取得的可翻译消息。

-- In package messages_app
procedure check_departments_overbudget is
  l_overbudget apex_t_varchar2;
begin
  for j in (
    select deptno,
           sum(coalesce(sal,0) + coalesce(comm,0)) as total_comp
    from emp
    group by deptno
    having sum(coalesce(sal,0) + coalesce(comm,0)) > 12000
    order by deptno) loop
    apex_string.push(
      l_overbudget,
      apex_string.format('%s (%s)',j.deptno,j.total_comp));
  end loop;

  if l_overbudget is not null then
    apex_error.add_error (
      p_message => apex_lang.get_message(
        'OVERBUDGET_DEPARTMENTS',
        apex_t_varchar2(
          'department_list',apex_string.join(l_overbudget,', '),
          'amount' ,'12000')),
      p_display_location => apex_error.c_on_error_page);
  end if;
end check_departments_overbudget;

这类检查不能放在普通验证中,因为验证运行在 Processing 阶段之前,看不到保存进程即将提交的数据。把它放在保存进程之后的页面进程中,既能看到待提交变更,又能通过错误否决事务。

图 20-12 聚合 SQL 可以用 ADD_ERROR 否决事务。

20.1.5.2 使用 JavaScript 显示消息#

客户端逻辑可以调用 apex.message.showPageSuccess 显示成功消息,调用 apex.message.showErrors 显示一个或多个页面级或字段级错误,并用 apex.message.clearErrors 清除所有错误或某个页面项的错误。

下面示例补充一个 CHECK_DEPTNO 函数,用于即时验证部门编号。

-- In package messages_app
function check_deptno(
  p_deptno in number)
  return varchar2
is
begin
  for j in (select null from dept where deptno = p_deptno) loop
    return null; -- valid
  end loop;
  return apex_lang.get_message('INVALID_DEPTNO'); -- invalid
end;

目标是在用户提交页面前,同时对 P5_EMPNOP5_DEPTNO 做服务器端检查,并把错误显示在对应字段下方。

图 20-13 提交前显示即时字段级错误。

P5_EMPNO 的 Change 事件动态操作中,先执行 JavaScript 清除该字段已有错误。

apex.message.clearErrors('P5_EMPNO');
图 20-14 检查变更后的 Employee ID 前先清除旧错误。

然后 Execute Server-side Code 动作调用服务器端函数,把返回的错误消息写入隐藏页面项 P5_INVALID_MESSAGE

:P5_INVALID_MESSAGE := messages_app.check_empno(:P5_EMPNO);
图 20-15 在服务器端验证 Employee ID。

最后用 showErrors 把服务器返回的消息显示在 P5_EMPNO 字段附近。该动作应配置客户端条件,仅在 P5_INVALID_MESSAGE 不为空时执行。

apex.message.showErrors(
  [
    {
      type: "error",
      location: [ "inline" ],
      pageItem: "P5_EMPNO",
      message: $v('P5_INVALID_MESSAGE')
    }
  ] );

如果错误消息文本包含 HTML 标记,可向 showErrors 传入额外属性 unsafe: false,避免尖括号等保留字符被转义。

图 20-16 条件性显示服务器端检查返回的错误。

验证 P5_DEPTNO 时复用同一模式:提交部门编号,调用 CHECK_DEPTNO,把错误放到同一个隐藏项,再把消息显示在 P5_DEPTNO 字段上。

图 20-17 复用相同方案即时验证 Department ID。

20.1.6 自定义错误消息显示#

错误处理函数(Error Handling Function)可以在 APEX 把服务器端错误报告给用户前介入,适合把数据库约束名、触发器异常或内部错误转换成更友好的消息。

20.1.6.1 观察默认错误行为#

为了理解自定义错误处理的价值,可以先看默认行为。假设在 EMP 表上增加约束,要求佣金不能超过工资的两倍。

alter table emp add constraint comm_less_than_twice_sal
check (comm < 2 * sal);

默认情况下,违反该约束时,用户会看到包含约束名的数据库错误,例如 ORA-02290: check constraint (SCHEMA.COMM_LESS_THAN_TWICE_SAL) violated。这对开发者有用,但对普通用户不够友好。

图 20-18 默认情况下用户会看到约束违反错误名。

APEX 引擎内部错误也类似。默认消息可能说明技术原因,并提示联系应用管理员,例如表单区域没有定义主键项。

图 20-19 默认情况下用户会看到内部错误提示。

20.1.6.2 指定错误处理函数#

APEX 引擎会在报告服务器端错误前调用错误处理函数。函数名称可以自定义,但签名必须接收 apex_error.t_error,并返回 apex_error.t_error_result

-- In package messages_app
function apex_error_handling (
  p_error in apex_error.t_error )
  return apex_error.t_error_result;

实际项目中通常把函数放在应用包中,例如 MESSAGES_APP。然后在页面属性的 Error Handling Function 中填写函数名。

图 20-20 配置页面的错误处理函数。

20.1.6.3 阅读错误处理函数代码#

典型错误处理函数会先调用 apex_error.init_error_result 初始化返回结果。对于少见的内部错误,它记录日志,并把用户看到的消息替换成通用文本消息。对于数据库约束错误,它提取约束名,再尝试查找对应的可翻译文本消息。

-- In package messages_app
function apex_error_handling (
  p_error in apex_error.t_error )
  return apex_error.t_error_result
is
  l_result          apex_error.t_error_result;
  l_constraint_name varchar2(255);
  l_error_message   varchar2(255);
begin
  l_result := apex_error.init_error_result(p_error);

  if p_error.is_internal_error then
    if not p_error.is_common_runtime_error then
      add_error_log(p_error);
      l_result.message := apex_lang.get_message('UNEXPECTED_INTERNAL_ERROR');
      l_result.additional_info := null;
    end if;
  else
    l_result.display_location := apex_error.c_inline_in_notification;

    if p_error.ora_sqlcode in (-1, -2091, -2290, -2291, -2292) then
      l_constraint_name := apex_error.extract_constraint_name(
        p_error => p_error );
      begin
        l_error_message :=
          apex_lang.get_message('CONSTRAINT_' || l_constraint_name);

        if l_error_message != 'CONSTRAINT_' || l_constraint_name then
          l_result.message := l_error_message;
        end if;
      exception
        when no_data_found then
          null;
      end;
    end if;

    if p_error.ora_sqlcode is not null
       and l_result.message = p_error.message then
      l_result.message := apex_error.get_first_ora_error_text(
        p_error => p_error );
    end if;
  end if;

  return l_result;
end apex_error_handling;

这里的约束消息约定是:如果约束名为 XXX,则查找文本消息 CONSTRAINT_XXX。如果找到了真正的文本,就把它作为用户消息;如果没有,就退回到简化后的 ORA 错误文本。

20.1.6.4 记录错误信息用于排查#

辅助过程 ADD_ERROR_LOG 接收 APEX 传入的 T_ERROR 记录,把错误上下文写入自定义日志表。日志表可以保存应用、页面、用户、浏览器、IP、页面项、区域、列、行号、APEX 错误码、ORA 错误码、调用栈等信息。

create table messages_app_errors (
  id number generated by default on null as identity not null,
  err_time timestamp(6) default systimestamp,
  app_id number,
  app_page_id number,
  app_user varchar2(512),
  user_agent varchar2(4000),
  ip_address varchar2(512),
  ip_address2 varchar2(512),
  message varchar2(4000),
  page_item_name varchar2(255),
  region_id number,
  column_alias varchar2(255),
  row_num number,
  apex_error_code varchar2(255),
  ora_sqlcode number,
  ora_sqlerrm varchar2(4000),
  error_backtrace varchar2(4000),
  procedure_name varchar2(1000),
  error_text varchar2(4000),
  constraint message_app_errors_id_pk primary key (id)
);

日志过程使用自治事务(pragma autonomous_transaction),并在插入日志后显式 commit。这样即使 APEX 因错误回滚业务数据,排查日志也能保留下来。

-- In package messages_app
procedure add_error_log (
  p_error in apex_error.t_error,
  p_procedure_name in varchar2 default null,
  p_error_text in varchar2 default null)
is
  pragma autonomous_transaction;
begin
  delete from messages_app_errors
  where err_time <= current_timestamp - 21;

  insert into messages_app_errors (
    app_id, app_page_id, app_user, user_agent,
    ip_address, ip_address2, message, page_item_name,
    region_id, column_alias, row_num, apex_error_code,
    ora_sqlcode, ora_sqlerrm, error_backtrace,
    procedure_name, error_text)
  select v('APP_ID'),
         v('APP_PAGE_ID'),
         v('APP_USER'),
         owa_util.get_cgi_env('HTTP_USER_AGENT'),
         owa_util.get_cgi_env('REMOTE_ADDR'),
         sys_context('USERENV', 'IP_ADDRESS'),
         substr(p_error.message,0,4000),
         p_error.page_item_name,
         p_error.region_id,
         p_error.column_alias,
         p_error.row_num,
         p_error.apex_error_code,
         p_error.ora_sqlcode,
         substr(p_error.ora_sqlerrm,0,4000),
         substr(p_error.error_backtrace,0,4000),
         p_procedure_name,
         p_error_text
  from dual;

  commit;
end add_error_log;

完整字段可参考 APEX_ERROR 包文档中 T_ERROR 记录的说明。

20.1.6.5 体验自定义错误处理#

配置错误处理函数后,可以通过约束违反、数据库异常和内部错误来验证效果。下面的触发器故意制造两种数据库错误:当 COMM 为 1234 时触发除零错误;当 COMM 为 1235 时抛出 RAISE_APPLICATION_ERROR

create or replace trigger emp_conditional_error_test
before insert or update on emp
for each row
begin
  if :new.comm = 1234 then
    if :new.comm / 0 > 1 then
      null;
    end if;
  elsif :new.comm = 1235 then
    raise_application_error(-20020,'Some error from the database');
  end if;
end;

当用户输入的佣金超过工资两倍时,错误处理函数可以把约束错误转换为文本消息 CONSTRAINT_COMM_LESS_THAN_TWICE_SAL 对应的友好文案。

图 20-21 错误处理函数为约束违反显示自定义消息。

当用户输入 1235 时,页面显示的是简化后的数据库异常文本,而不是完整技术堆栈。

图 20-22 错误处理函数显示简化后的数据库异常消息。

当用户输入 1234 造成除零错误时,用户也只看到简化后的错误,例如 divisor is equal to zero

对于少见的内部 APEX 错误,错误处理函数可以向用户显示通用消息,同时把详细信息记录到日志表。例如紧急修复时误关表单主键项的 Primary Key 开关,生产用户可能只看到“发生了意外的内部应用错误”。

图 20-23 少见内部错误显示自定义通用消息。

管理员可以查看只对管理员开放的日志页面,定位错误发生在哪个应用和页面。

图 20-24 错误处理函数记录少见内部错误供复核。

进入详情后,可以看到错误处理函数记录的更多上下文,从而快速修复并重新发布应用。

图 20-25 查看少见内部错误的日志详情。

20.2 使用模板发送电子邮件#

APEX 可以通过原生 Send E-Mail 页面进程或工作流活动发送邮件。任务定义和自动化中的原生动作类型也遵循同样模式。需要在业务逻辑中发邮件时,可以使用 APEX_MAIL 包的 SEND 过程或函数。

邮件发送通常进入队列。可以通过 APEX_MAIL_QUEUE 查看待发送邮件,通过 APEX_MAIL_LOG 查看近期已发送邮件。

20.2.1 设置默认发件人地址#

原生 Send E-Mail 功能默认使用 &APP_MAIL. 作为 From 属性。这个值来自应用定义 Properties 区域中的 Application Email From Address。

APEX 实例管理员负责配置供所有工作区使用的邮件服务器设置,普通应用开发者通常只需要配置应用自身的默认发件人地址。

20.2.2 不使用模板发送电子邮件#

一次性邮件可以直接在页面进程中写纯文本或 HTML 正文,并使用替换字符串填入页面数据。示例中的 Welcome Packet 邮件使用原生 Send E-Mail 页面进程,收件人来自 &P34_EMPLOYEE_EMAIL.,同时配置主题、纯文本正文和用于附加 ZIP 文件的 SQL。

图 20-26 使用原生页面进程发送不带模板的邮件。

20.2.3 定义带指令的电子邮件模板#

电子邮件模板把格式与每次发送时变化的数据、附件分离。发送时引用现有模板,设置占位符值,并按需提供附件即可。

模板的 Static Identifier 很重要,PL/SQL API 通过它引用模板。模板正文可以使用 HTML Template Directives,根据占位符是否有值决定是否输出某段内容。

图 20-27 模板具有供 API 使用的 Static ID,并可使用模板指令。

示例模板包含 EMPLOYEE_NAMENEW_SALARYNEW_COMMISSION 占位符。只有 NEW_COMMISSION 有值时,模板才通过 {if/}{endif/} 输出新佣金短语。

{if NEW_COMMISSION /}
, and your commission is now #NEW_COMMISSION#
{endif/}
图 20-28 定义带占位符的电子邮件模板。

20.2.4 使用电子邮件模板和占位符#

原生 Send E-Mail 动作、页面进程或工作流活动使用邮件模板时,需要为当前邮件设置占位符值。占位符值可以来自列、页面项、工作流变量、应用项或系统替换值,具体取决于当前上下文。

示例自动化的 Send Email 动作使用 Employee Salary Lottery Winner 模板,并把三项占位符映射到当前自动化行的列值:EMPLOYEE_NAME 对应 &ENAME.NEW_SALARY 对应 &NEW_SALARY.NEW_COMMISSION 对应 &NEW_COMMISSION.

图 20-29 为 Send Email 动作配置模板和占位符值。

20.2.5 从业务逻辑发送电子邮件#

如果需要从 PL/SQL 业务逻辑发送同一封模板邮件,可以调用 APEX_MAIL.SEND。未传 p_from 时,它默认使用 APP_EMAIL 的值。p_placeholders 参数需要 JSON 文档;APEX_STRING.PLIST_TO_JSON_CLOB 可以把名称和值的字符串列表转换成 JSON。

apex_mail.send(
  p_to                 => :EMPLOYEE_EMAIL,
  p_template_static_id => 'EMPLOYEE_SALARY_LOTTERY_WINNER',
  p_placeholders       => apex_string.plist_to_json_clob(
    apex_t_varchar2(
      'EMPLOYEE_NAME' , :ENAME,
      'NEW_SALARY'    , :NEW_SALARY,
      'NEW_COMMISSION', :NEW_COMMISSION)));

字符串列表中的奇数位置是 JSON 属性名,偶数位置是对应值。

20.3 发送推送通知#

APEX 可以通过原生 Send Push Notification 页面进程或工作流活动向已选择接收通知的用户发送推送通知。任务定义和自动化中的原生动作类型也按同样方式工作。

从业务逻辑发送推送通知时,使用 APEX_PWA.SEND_PUSH_NOTIFICATION。待发送推送通知可以通过 APEX_PUSH_NOTIFICATIONS_QUEUE 查看。

20.3.1 在应用中启用推送通知#

要启用推送通知,在 Shared Components 的 Progressive Web App 区域中打开 Enable Progressive Web App 和 Enable Push Notifications 两个开关。

某些移动平台要求推送通知与已安装的 Progressive Web App (PWA) 关联,因此通常也应考虑启用 Installable,让用户可以一键或轻触安装应用。安装后,应用会像原生应用一样出现在桌面或移动设备上。

图 20-30 在应用中启用推送通知。

20.3.2 为推送通知生成密钥对#

推送通知使用公钥/私钥凭据进行加密和签名。点击 Generate Credentials 可以生成新的 Key Pair 凭据,并将其选为推送通知使用的 Credentials。

生成凭据后,该按钮会变成 Regenerate Credentials。把应用部署到新环境,或需要更换推送通知签名密钥时,需要重新生成凭据。更换密钥后,用户通常需要重新选择接收通知。

图 20-31 生成签名推送通知的密钥对凭据。

20.3.3 让用户管理通知设置#

推送通知只会发送给已经选择接收通知的用户。为了让用户自行管理偏好,可以在 Progressive Web App 应用设置页的 Push Notifications 区域点击 Add Settings Page,让 APEX 添加设置页面和菜单入口。

图 20-32 为应用添加推送通知设置页面。

20.3.4 选择接收推送通知#

最终用户在每台设备上从已登录用户菜单进入设置,选择接收推送通知。例如用户 Lucy 打开 Settings,进入 Push Notifications,启用 Enable push notifications on this device,然后在系统提示中允许通知。

这个用户偏好会持续保存,直到用户关闭。如果应用更换了 Key Pair 凭据,用户需要重新选择接收通知。

图 20-33 Lucy 通过应用设置选择接收通知。

20.3.5 给同事推送一条笑话#

推送通知常用于重要事件,也可以传递轻量消息。示例页面 Send Chuckle 让用户选择一位同事,并把随机生成的一句话作为推送通知发给对方。

图 20-34 用户发送带随机轻松内容的推送通知。

页面先使用名为 Get Random Chuckle 的 Invoke API 页面进程调用 RANDOM_CHUCKLE 函数,把结果放入隐藏页面项 P11_RANDOM_CHUCKLE。随后 Send Push Notification 页面进程把通知发给 P11_USERNAME 选择列表中的用户名。

APEX 会自动把通知发送给该用户已订阅的设备。若目标用户没有选择接收通知,APEX 会直接忽略该用户,不需要开发者自己维护订阅列表。

图 20-35 用原生页面进程发送推送通知。

已选择接收推送的 Lucy 会在手机和配对手表上收到这条消息。

图 20-36 Lucy 收到通知。

20.3.6 覆盖通知目标链接#

默认情况下,用户点击或轻触推送通知会打开应用首页。也可以配置 Target Link,让通知跳转到应用中的特定页面。

要让目标链接按预期工作,应使用绝对 URL,并在应用 Security 设置的 Session Management 区域启用 Deep Linking。

20.3.7 从业务逻辑发送推送通知#

如果要从 PL/SQL 业务逻辑发送同样的推送通知,可以调用 APEX_PWA.SEND_PUSH_NOTIFICATION。可选参数 p_target_urlp_icon_url 可用于覆盖默认目标链接和图标。

apex_pwa.send_push_notification(
  p_user_name => :P11_USERNAME,
  p_title     => 'Need a Chuckle?',
  p_body      => :P11_RANDOM_CHUCKLE);