-- ============================================================================= -- SIP 认证(apt_sip_auth) -- 定位:分机在 Asterisk 上的 auth 账号,与 endpoint 1:1(可先建认证后绑分机)。 -- 多租户/多实例: -- * 唯一性 = (gyid, instance_code, username) —— 同一实例下用户名唯一; -- 不同公寓或不同 Asterisk 实例可出现同名用户名,但彼此不可见; -- * 所有读写必须同时带 gyid + instance_code,禁止只按 username 查(跨公寓串号); -- * auth_id 采用 AUTH--,在实例内全局唯一,供 ps_auths.id 使用。 -- 口令策略: -- * secret_mode='PLAIN':库内口令,默认 psw.voip(内网/联调用); -- * secret_mode='ENV' :只存环境变量名 secret_ref,取值在渲染/同步时注入, -- 生产建议用这一档(与 APT_AMI_SECRET 同一原则,不把口令写进库与日志); -- * 两种模式都保留 secret_version / last_rotated_at,便于轮换与审计。 -- 幂等:建表 IF NOT EXISTS;种子与视图可重复执行。 -- ============================================================================= SET NAMES utf8mb4; CREATE TABLE IF NOT EXISTS apt_sip_auth ( id BIGINT NOT NULL AUTO_INCREMENT, gyid VARCHAR(32) NOT NULL COMMENT '公寓标识(租户隔离键)', instance_code VARCHAR(64) NOT NULL COMMENT 'Asterisk 实例(apt_ast_instance.instance_code)', username VARCHAR(64) NOT NULL COMMENT 'SIP 用户名/分机号', auth_id VARCHAR(128) NOT NULL COMMENT 'Asterisk ps_auths.id,约定 AUTH--', endpoint_id VARCHAR(64) NULL COMMENT '关联分机(1:1,可空以便先建认证后绑分机)', auth_type VARCHAR(20) NOT NULL DEFAULT 'userpass' COMMENT 'Asterisk auth_type', secret_mode VARCHAR(10) NOT NULL DEFAULT 'PLAIN' COMMENT 'PLAIN=库内口令 / ENV=环境变量', secret_plain VARCHAR(128) NOT NULL DEFAULT 'psw.voip' COMMENT 'secret_mode=PLAIN 时的口令;空串表示未设置', secret_ref VARCHAR(64) NULL COMMENT 'secret_mode=ENV 时的环境变量名', secret_version INT NOT NULL DEFAULT 1 COMMENT '口令版本,轮换时 +1', realm VARCHAR(64) NOT NULL DEFAULT 'asterisk' COMMENT 'Asterisk auth realm', state VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/DISABLED/ROTATING', enabled TINYINT NOT NULL DEFAULT 1, last_rotated_at DATETIME(3) NULL, 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_sipauth_gyid_auth_id (gyid, auth_id), UNIQUE KEY uk_sipauth_instance_username (gyid, instance_code, username), UNIQUE KEY uk_sipauth_instance_endpoint (gyid, instance_code, endpoint_id), KEY idx_sipauth_gyid_state (gyid, instance_code, state, enabled), KEY idx_sipauth_gyid_endpoint (gyid, endpoint_id), CONSTRAINT ck_sipauth_secret_mode CHECK (secret_mode IN ('PLAIN','ENV')), CONSTRAINT ck_sipauth_state CHECK (state IN ('ACTIVE','DISABLED','ROTATING')), CONSTRAINT ck_sipauth_auth_type CHECK (auth_type IN ('userpass','md5','userpass,md5')), CONSTRAINT ck_sipauth_secret_ref CHECK (secret_mode = 'PLAIN' OR secret_ref IS NOT NULL) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='SIP 认证账号(按公寓 + Asterisk 实例隔离;默认口令 psw.voip 仅限内网/联调)'; -- ----------------------------------------------------------------------------- -- 种子:为已登记分机补认证账号(每个公寓取第一个启用的实例;已存在则跳过) -- ----------------------------------------------------------------------------- INSERT INTO apt_sip_auth (gyid, instance_code, username, auth_id, endpoint_id, secret_plain) SELECT e.gyid, i.instance_code, e.extension, CONCAT('AUTH-', i.instance_code, '-', e.extension), e.endpoint_id, 'psw.voip' FROM apt_sip_endpoint e JOIN apt_ast_instance i ON i.id = (SELECT i2.id FROM apt_ast_instance i2 WHERE i2.gyid = e.gyid AND i2.deleted = 0 AND i2.enabled = 1 ORDER BY i2.id LIMIT 1) LEFT JOIN apt_sip_auth a ON a.gyid = e.gyid AND a.instance_code = i.instance_code AND a.username = e.extension WHERE e.deleted = 0 AND a.id IS NULL; -- ----------------------------------------------------------------------------- -- Asterisk realtime 视图:ps_auths / ps_endpoints(多实例按 gyid + instance_code 过滤) -- ENV 模式在视图中不回显口令(password 为 NULL),由同步器渲染后再下发。 -- ----------------------------------------------------------------------------- CREATE OR REPLACE VIEW ps_auths AS SELECT auth_id AS id, auth_type, username, CASE WHEN secret_mode = 'PLAIN' THEN secret_plain END AS password, realm FROM apt_sip_auth WHERE deleted = 0 AND enabled = 1 AND state = 'ACTIVE'; CREATE OR REPLACE VIEW ps_endpoints AS SELECT e.endpoint_id AS id, 'transport-udp' AS transport, a.aor_id AS aors, au.auth_id AS auth, 'from-internal' AS context, 'ulaw' AS allow, 'all' AS disallow, 'no' AS direct_media, e.extension AS callerid, 'yes' AS rewrite_contact FROM apt_sip_endpoint e JOIN apt_sip_aor a ON a.gyid = e.gyid AND a.endpoint_id = e.endpoint_id AND a.deleted = 0 LEFT JOIN apt_sip_auth au ON au.gyid = e.gyid AND au.endpoint_id = e.endpoint_id AND au.deleted = 0 AND au.enabled = 1 AND au.state = 'ACTIVE' WHERE e.deleted = 0 AND e.enabled = 1; -- ----------------------------------------------------------------------------- -- 校验 -- ----------------------------------------------------------------------------- SELECT '=== 36. apt_sip_auth 列数(应 20)===' AS section; SELECT COUNT(*) AS auth_columns FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apt_sip_auth'; SELECT '=== 37. 唯一键(应 3:auth_id / instance+username / instance+endpoint)===' 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_auth' AND NON_UNIQUE = 0 GROUP BY INDEX_NAME; SELECT '=== 38. 默认口令 ===' AS section; SELECT COLUMN_NAME, COLUMN_DEFAULT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apt_sip_auth' AND COLUMN_NAME IN ('secret_plain','secret_mode'); SELECT '=== 39. 现有认证账号(按实例分布)===' AS section; SELECT gyid, instance_code, COUNT(*) AS accounts, SUM(secret_mode = 'PLAIN') AS plain_accounts FROM apt_sip_auth WHERE deleted = 0 GROUP BY gyid, instance_code;