Files
jcgl-dev/dev/sql/apt_rev01/22_sip_endpoint_room.sql
T

132 lines
7.8 KiB
SQL
Raw Normal View History

2026-11-12 13:36:24 +08:00
-- =============================================================================
-- SIP 分机 / AOR / Contact 与房间的统一管理(承接 03_domain_03_voice.sql)
-- 口径:
-- 1) apt_sip_endpoint = 分机(PJSIP endpoint),一房一分机,extension 不可改号;
-- 2) apt_sip_aor = 分机对应的 AOR(1:1,max_contacts=1 与文件一致);
-- 3) apt_sip_contact = AOR 下的 contact(话机 IP 绑定事实,含历史);
-- 4) apt_sip_binding = 房间 ↔ 分机的生效区间(历史可追溯,ACTIVE 唯一);
-- 5) 同一 gyid 下「一个 IP 只能有一个 ACTIVE contact」= 同一 IP 不得重复绑定;
-- 用生成列 + 唯一索引在库层兜底,服务层再做一次可读的冲突前置校验。
-- 幂等:建表 IF NOT EXISTS,补列/补索引走 information_schema 判断 + 动态 SQL。
-- =============================================================================
SET NAMES utf8mb4;
-- -----------------------------------------------------------------------------
-- 1. endpoint 补列:话机 IP(与 contact 一致)与来源
-- -----------------------------------------------------------------------------
CALL apt_add_column_if_missing('apt_sip_endpoint', 'ip_address',
"ip_address VARCHAR(64) NULL COMMENT '话机 IP(与 ACTIVE contact 的 ip_address 一致,解绑后清空)'");
CALL apt_add_column_if_missing('apt_sip_endpoint', 'source',
"source VARCHAR(20) NOT NULL DEFAULT 'ROOM' COMMENT 'ROOM=随房间自动创建 / MANUAL=手工登记'");
-- -----------------------------------------------------------------------------
-- 2. AOR 表(与 endpoint 1:1)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS apt_sip_aor (
id BIGINT NOT NULL AUTO_INCREMENT,
gyid VARCHAR(32) NOT NULL,
aor_id VARCHAR(64) NOT NULL COMMENT 'AOR 标识,如 AOR-<endpointId 后缀>',
endpoint_id VARCHAR(64) NOT NULL COMMENT '所属分机(1:1)',
max_contacts INT NOT NULL DEFAULT 1 COMMENT 'Asterisk max_contacts',
remove_existing TINYINT NOT NULL DEFAULT 1 COMMENT 'Asterisk remove_existing:新注册顶掉旧 contact',
default_expiration INT NOT NULL DEFAULT 3600 COMMENT 'contact 默认过期秒数',
qualify_frequency INT NOT NULL DEFAULT 30 COMMENT 'OPTIONS 探测间隔秒',
sync_state VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '仅 PENDING/SYNCED/FAILED/DRIFT',
config_version BIGINT NOT NULL DEFAULT 1,
deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_aor_gyid_id (gyid, aor_id),
UNIQUE KEY uk_aor_gyid_endpoint (gyid, endpoint_id),
KEY idx_aor_gyid_sync (gyid, sync_state),
CONSTRAINT ck_aor_sync CHECK (sync_state IN ('PENDING','SYNCED','FAILED','DRIFT'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='SIP AOR(与 endpoint 1:1,先库后 Asterisk)';
-- -----------------------------------------------------------------------------
-- 3. Contact 表(话机 IP 绑定事实)
-- active_ip 生成列:仅 ACTIVE 且未删除时取值,配合唯一索引实现
-- 「同 gyid 下一个 IP 只能有一个 ACTIVE contact」。
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS apt_sip_contact (
id BIGINT NOT NULL AUTO_INCREMENT,
gyid VARCHAR(32) NOT NULL,
contact_id VARCHAR(64) NOT NULL,
aor_id VARCHAR(64) NOT NULL,
endpoint_id VARCHAR(64) NOT NULL,
uri VARCHAR(255) NOT NULL COMMENT 'sip:<extension>@<ip>:<port>',
ip_address VARCHAR(64) NOT NULL,
port INT NOT NULL DEFAULT 5060,
transport VARCHAR(8) NOT NULL DEFAULT 'udp',
user_agent VARCHAR(128) NULL,
source VARCHAR(20) NOT NULL DEFAULT 'STATIC' COMMENT 'STATIC=手工绑定 / DYNAMIC=注册上报',
state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/RELEASED/EXPIRED',
last_seen_at DATETIME(3) NULL,
released_at DATETIME(3) NULL,
release_remark VARCHAR(128) NULL,
active_ip VARCHAR(64) GENERATED ALWAYS AS
(CASE WHEN state = 'ACTIVE' AND deleted = 0 THEN ip_address END) STORED,
deleted TINYINT NOT NULL DEFAULT 0,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_contact_gyid_id (gyid, contact_id),
UNIQUE KEY uk_contact_gyid_active_ip (gyid, active_ip),
KEY idx_contact_gyid_endpoint (gyid, endpoint_id, state),
KEY idx_contact_gyid_aor (gyid, aor_id, state),
CONSTRAINT ck_contact_state CHECK (state IN ('ACTIVE','RELEASED','EXPIRED'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='SIP contact(话机 IP 绑定事实,保留历史;同 IP 唯一 ACTIVE)';
-- -----------------------------------------------------------------------------
-- 4. binding 兜底:一个 endpoint 同时只能有一条 ACTIVE 绑定
-- -----------------------------------------------------------------------------
SET @apt_binding_col = (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apt_sip_binding'
AND COLUMN_NAME = 'active_endpoint');
SET @apt_sql_binding_col = IF(@apt_binding_col = 0,
'ALTER TABLE apt_sip_binding ADD COLUMN active_endpoint VARCHAR(64) GENERATED ALWAYS AS (CASE WHEN state = ''ACTIVE'' AND deleted = 0 THEN endpoint_id END) STORED',
'SELECT 1');
PREPARE stmt_binding_col FROM @apt_sql_binding_col;
EXECUTE stmt_binding_col;
DEALLOCATE PREPARE stmt_binding_col;
SET @apt_binding_idx = (SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apt_sip_binding'
AND INDEX_NAME = 'uk_binding_gyid_active_ep');
SET @apt_sql_binding_idx = IF(@apt_binding_idx = 0,
'ALTER TABLE apt_sip_binding ADD UNIQUE KEY uk_binding_gyid_active_ep (gyid, active_endpoint)',
'SELECT 1');
PREPARE stmt_binding_idx FROM @apt_sql_binding_idx;
EXECUTE stmt_binding_idx;
DEALLOCATE PREPARE stmt_binding_idx;
-- -----------------------------------------------------------------------------
-- 5. 校验
-- -----------------------------------------------------------------------------
SELECT '=== 32. SIP 表就绪(应 4:endpoint/binding/aor/contact)===' AS section;
SELECT COUNT(*) AS sip_tables
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME IN ('apt_sip_endpoint','apt_sip_binding','apt_sip_aor','apt_sip_contact');
SELECT '=== 33. endpoint 补列(应 2)===' AS section;
SELECT COUNT(*) AS endpoint_columns
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apt_sip_endpoint'
AND COLUMN_NAME IN ('ip_address','source');
SELECT '=== 34. 同 IP 唯一 ACTIVE 的库层兜底(应 1)===' AS section;
SELECT INDEX_NAME, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS cols
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apt_sip_contact'
AND INDEX_NAME = 'uk_contact_gyid_active_ip'
GROUP BY INDEX_NAME;
SELECT '=== 35. 一 endpoint 仅一条 ACTIVE 绑定(应 1)===' AS section;
SELECT INDEX_NAME, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS cols
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apt_sip_binding'
AND INDEX_NAME = 'uk_binding_gyid_active_ep'
GROUP BY INDEX_NAME;