-- ========================================================================= -- 存量升级:D20 对外房间门禁服务接口 P1(v1 库) -- -- 背景:向外部业务系统(PMS/访客/园区平台)开放入住、退房、续住能力。 -- 对外接口复用住宿模块(lock_room / lock_stay)与授权下发(lock_auth), -- 本期只补三处数据结构: -- 1) lock_stay 增加业务系统归属字段,用于幂等与数据隔离; -- 2) lock_person_ref 把外部人员标识映射到平台 person_no(按应用隔离); -- 3) lock_room_device_history 记录房间-门锁绑定历史,支撑后续 -- 「开锁记录查询包含历史门锁」(P3)。 -- -- 本文档对应设计:doc/对外开放房间门禁服务接口设计.md §9 -- -- 执行前置: -- 1) 备份 lock_stay / lock_room / lock_device; -- 2) 与 D20 代码同批次发布:新代码写 biz_no/biz_system/source 三列, -- 缺列会导致接口入住直接失败(平台页面入住不受影响); -- 3) 低峰期执行;alter 段锁表时间取决于 lock_stay 行数。 -- -- 可重复执行说明:建表用 if not exists、回填用 not exists 保护; -- MySQL 不支持 add column / add index if not exists,第 1 节重复执行会报 -- "Duplicate column name 'biz_no'" 或 "Duplicate key name",忽略即可继续。 -- -- 未包含:lock_callback_task(结果回调与重试,P2 交付时再建)。 -- ========================================================================= set names utf8mb4; -- ========================================================================= -- 1、lock_stay 增加业务系统字段 -- biz_system + biz_no 为对外幂等键:业务号只在应用内唯一,平台手工单 -- 两者均为 NULL(MySQL 唯一索引允许多行 NULL,不会互相冲突)。 -- source 区分单据来源,便于页面与对账区分「平台办理」和「接口办理」。 -- ========================================================================= alter table lock_stay add column biz_no varchar(64) default null comment '业务系统单号(应用内唯一,平台手工单为空)' after stay_no, add column biz_system varchar(32) default null comment '业务系统编码(iot.openapi.apps[].app-code,平台手工单为空)' after biz_no, add column source char(1) default '1' comment '单据来源(1平台 2接口)' after biz_system; alter table lock_stay add unique key uk_lock_stay_biz (biz_system, biz_no); -- ========================================================================= -- 2、外部人员标识映射表 -- 业务系统的人员标识格式不受 person_no(2 字母 + 6 数字)约束,因此单独 -- 建映射;(app_code, external_id) 唯一,同一自然人在不同应用各自建档。 -- ========================================================================= create table if not exists lock_person_ref ( ref_id bigint(20) not null auto_increment comment '映射ID', person_id bigint(20) not null comment '人员ID(lock_person.person_id)', person_no varchar(32) not null comment '人员编号(冗余协议键)', app_code varchar(32) not null comment '接入应用编码(iot.openapi.apps[].app-code)', external_id varchar(64) not null comment '业务系统人员标识(假定稳定不变)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', remark varchar(500) default null comment '备注', primary key (ref_id), unique key uk_lock_person_ref (app_code, external_id), key idx_lock_person_ref_person (person_no) ) engine=innodb comment = '外部人员标识映射表'; -- ========================================================================= -- 3、房间-门锁绑定历史表 -- 只增不改(解绑时回填 unbind_time):房间换锁后,历史时段内的开锁记录 -- 仍要能按房间查出来,因此需要保留「哪段时间绑的是哪把锁」。 -- ========================================================================= create table if not exists lock_room_device_history ( history_id bigint(20) not null auto_increment comment '历史ID', room_id bigint(20) not null comment '房间ID(lock_room.room_id)', device_id bigint(20) not null comment '门锁ID(lock_device.device_id)', device_sn varchar(32) default null comment '门锁SN(冗余快照)', bind_time datetime not null comment '绑定生效时间', unbind_time datetime default null comment '解绑时间(为空表示当前仍在绑)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', remark varchar(500) default null comment '备注', primary key (history_id), key idx_lock_room_device_history (room_id, bind_time, unbind_time), key idx_lock_room_device_history_device (device_id) ) engine=innodb comment = '房间门锁绑定历史表'; -- ========================================================================= -- 4、存量回填:把房间当前绑定登记为一条未解绑的历史 -- bind_time 取房间的 update_time(最近一次绑定/换锁),缺失时退到 -- create_time,再缺失取当前时间。 -- ========================================================================= insert into lock_room_device_history (room_id, device_id, device_sn, bind_time, create_by, create_time, remark) select r.room_id, r.device_id, d.device_sn, ifnull(r.update_time, ifnull(r.create_time, now())), 'migrate', now(), 'D20 存量回填:当前绑定视为历史起点' from lock_room r join lock_device d on d.device_id = r.device_id where r.del_flag = '0' and r.device_id is not null and not exists ( select 1 from lock_room_device_history h where h.room_id = r.room_id and h.device_id = r.device_id and h.unbind_time is null ); -- ========================================================================= -- 5、执行后核对(只读,按需执行) -- ========================================================================= -- 5.1 新增列与唯一键是否到位 -- select column_name, column_type, column_default, column_comment -- from information_schema.columns -- where table_schema = database() and table_name = 'lock_stay' -- and column_name in ('biz_no', 'biz_system', 'source'); -- -- select index_name, column_name from information_schema.statistics -- where table_schema = database() and table_name = 'lock_stay' -- and index_name = 'uk_lock_stay_biz'; -- -- 5.2 回填的历史行数应等于当前已绑锁的房间数 -- select (select count(1) from lock_room_device_history where unbind_time is null) as open_history_cnt, -- (select count(1) from lock_room where del_flag = '0' and device_id is not null) as bound_room_cnt; -- -- 5.3 存量入住单的 source 应全部为 1(平台),biz_no/biz_system 应为空 -- select source, count(1) from lock_stay group by source; -- -- 5.4 业务号重复检查(应为空) -- select biz_system, biz_no, count(1) from lock_stay -- where biz_no is not null group by biz_system, biz_no having count(1) > 1; -- ========================================================================= -- 5.5 修复:平台手工单被写成空串的 biz_no / biz_system(2026-11-01) -- 现象:入住管理第二次办理入住报 -- Duplicate entry '' for key 'lock_stay.uk_lock_stay_biz' -- 原因:唯一键只允许多行 NULL,平台单落的却是空串。 -- 代码已修正为空 → NULL;存量空串按下面语句归一,可重复执行。 -- 接口单的业务号必非空,这里的空串只可能来自平台手工单,不会误伤接口单据。 -- ========================================================================= update lock_stay set biz_no = null where biz_no = '' and (biz_system is null or biz_system = ''); update lock_stay set biz_system = null where biz_system = '' and biz_no is null; -- 核对:结果应为 0 -- select count(1) from lock_stay where biz_no = '' or biz_system = ''; -- ========================================================================= -- 6、回滚(仅在确认没有接口单据后执行) -- ========================================================================= -- 6.1 删除新增表 -- drop table if exists lock_room_device_history; -- drop table if exists lock_person_ref; -- -- 6.2 回退 lock_stay 字段(会丢失接口单据的业务号归属,执行前先确认) -- select count(1) from lock_stay where biz_no is not null; -- alter table lock_stay drop index uk_lock_stay_biz; -- alter table lock_stay drop column source, drop column biz_system, drop column biz_no;