Files
jcgl-dev/dev/sql/apt_rev01/22_sip_endpoint_room.sql
T
2026-11-12 13:36:24 +08:00

133 lines
7.8 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =============================================================================
-- 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;