-- ========================================================================= -- 存量升级:D20 对外房间门禁服务接口 P2(结果回调) -- -- 背景:P1 只做「同步受理」,门锁下发结果没有回传渠道。P2 在授权/清除命令 -- 进入终态时生成回调任务,按应用配置的地址上报,失败按退避重试,最终死信, -- 支持人工重发。本文档对应设计:doc/对外开放房间门禁服务接口设计.md §8、§9 -- -- 只新增一张表 + 两个字典 + 一组菜单,不改动 P1 已建的表结构。 -- -- 执行前置: -- 1) 先执行 upgrade_d20_openapi_stay.sql(P1),本脚本依赖入住单的 -- biz_no / biz_system 字段; -- 2) 与 P2 代码同批次发布:代码会在入住/退房/续住时写本表,缺表会导致 -- 这些操作在事务内报错回滚(受影响的只是接口单,平台手工单不受影响); -- 3) 每个接入应用需在 iot.openapi.apps[] 配置 callback-url 与 callback-secret: -- callback-url 为空视为「未开通回调」,不产生任务; -- callback-url 已配置但 callback-secret 为空时事件直接落死信(避免无签名报文), -- 补齐密钥后在页面人工重发即可。 -- -- 可重复执行:建表用 if not exists、字典与菜单先 delete 再 insert。 -- ========================================================================= set names utf8mb4; -- ========================================================================= -- 1、回调任务表 -- status:0 待结果(已登记事件,等设备回执)1 待发送 2 已成功 3 死信 -- revision:同一应用 + 同一单号 + 同一事件的结果版本,补偿成功后递增, -- 对方按 revision 最大者为最终结果(见设计稿 §8.3) -- callback_url 存事件产生时的配置快照,避免上线换地址后旧事件发错地方 -- ========================================================================= create table if not exists lock_callback_task ( task_id bigint(20) not null auto_increment comment '任务ID', event_id varchar(64) not null comment '事件ID(回调头 X-Callback-Id,对方据此去重)', app_code varchar(32) not null comment '接入应用编码(iot.openapi.apps[].app-code)', event_type varchar(32) not null comment '事件类型(STAY_CHECKIN_RESULT / STAY_CHECKOUT_RESULT / STAY_EXTEND_RESULT)', biz_no varchar(64) not null comment '业务系统单号', revision int(11) not null default 1 comment '同单同事件的版本(结果变更时递增)', auth_id bigint(20) default null comment '关联授权ID(lock_auth.auth_id),命令终态据此落库', callback_url varchar(500) default null comment '回调地址(事件产生时的应用配置快照)', payload text comment '回调报文原文(参与签名)', status char(1) not null default '0' comment '状态(0待结果 1待发送 2已成功 3死信,字典 lock_callback_status)', retry_count int(11) not null default 0 comment '已重试次数', next_retry_time datetime default null comment '下次发送时间', last_error varchar(500) default null comment '最后一次失败原因', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_time datetime comment '更新时间', primary key (task_id), unique key uk_lock_callback_event (event_id), unique key uk_lock_callback_rev (app_code, biz_no, event_type, revision), key idx_lock_callback_due (status, next_retry_time), key idx_lock_callback_auth (auth_id, status), key idx_lock_callback_list (app_code, create_time) ) engine=innodb comment = '开放接口结果回调任务表'; -- ========================================================================= -- 2、字典 -- ========================================================================= delete from sys_dict_data where dict_type in ('lock_callback_status', 'lock_callback_event'); delete from sys_dict_type where dict_type in ('lock_callback_status', 'lock_callback_event'); insert into sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark) values ('回调任务状态', 'lock_callback_status', '0', 'admin', now(), '开放接口结果回调任务状态'), ('回调事件类型', 'lock_callback_event', '0', 'admin', now(), '开放接口结果回调事件'); insert into sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) values (1, '待结果', '0', 'lock_callback_status', '', 'info', 'Y', '0', 'admin', now(), '已登记事件,等待设备回执'), (2, '待发送', '1', 'lock_callback_status', '', 'primary', 'N', '0', 'admin', now(), '报文就绪,等待投递'), (3, '已成功', '2', 'lock_callback_status', '', 'success', 'N', '0', 'admin', now(), '对方已接收'), (4, '死信', '3', 'lock_callback_status', '', 'danger', 'N', '0', 'admin', now(), '重试耗尽或配置缺失,需人工处理'), (1, '入住结果', 'STAY_CHECKIN_RESULT', 'lock_callback_event', '', 'primary', 'Y', '0', 'admin', now(), ''), (2, '退房结果', 'STAY_CHECKOUT_RESULT', 'lock_callback_event', '', 'warning', 'N', '0', 'admin', now(), ''), (3, '续住结果', 'STAY_EXTEND_RESULT', 'lock_callback_event', '', 'success', 'N', '0', 'admin', now(), ''); -- ========================================================================= -- 3、菜单:回调任务(2140 号段,接在住宿 2130 之后) -- ========================================================================= delete from sys_menu where perms like 'lock:callback:%'; insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) values (2140, '回调任务', 2000, 13, 'callback', 'iot/callback/index', '', '', 1, 0, 'C', '0', '0', 'lock:callback:list', 'message', 'admin', CURRENT_TIMESTAMP, '开放接口结果回调任务'); insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) values (2141, '回调查询', 2140, 1, '', null, '', '', 1, 0, 'F', '0', '0', 'lock:callback:query', '#', 'admin', CURRENT_TIMESTAMP, ''), (2142, '人工重发', 2140, 2, '', null, '', '', 1, 0, 'F', '0', '0', 'lock:callback:resend', '#', 'admin', CURRENT_TIMESTAMP, '死信重新投递'); -- ========================================================================= -- 4、执行后核对(只读,按需执行) -- ========================================================================= -- 4.1 表与索引是否到位(应返回 1 行,含 3 个索引) -- show create table lock_callback_task; -- 4.2 各状态任务数(上线初期除 0/1 外都应为 0) -- select status, count(1) as total from lock_callback_task group by status; -- 4.3 同一单号同一事件不应出现重复 revision(应为空) -- select app_code, biz_no, event_type, revision, count(1) c -- from lock_callback_task group by app_code, biz_no, event_type, revision having c > 1; -- 4.4 长期卡在「待结果」的任务(超过 24 小时,通常是被补偿任务覆盖或命令丢失) -- select task_id, app_code, biz_no, event_type, create_time -- from lock_callback_task -- where status = '0' and create_time < date_sub(now(), interval 1 day); -- ========================================================================= -- 5、回滚(仅在确认不再需要回调记录后执行) -- ========================================================================= -- drop table if exists lock_callback_task; -- delete from sys_menu where perms like 'lock:callback:%'; -- delete from sys_dict_data where dict_type in ('lock_callback_status', 'lock_callback_event'); -- delete from sys_dict_type where dict_type in ('lock_callback_status', 'lock_callback_event');