From 74213a363fc4cdb6e9d595a3ccc62c2012f0741c Mon Sep 17 00:00:00 2001 From: Nelson <1475262689@qq.com> Date: Tue, 11 Aug 2026 17:46:56 +0800 Subject: [PATCH] fix: separate voice credentials and refresh feature toggle --- ai_knowledge_base_v2/.env.example | 2 ++ .../src/components/SystemConfigView.vue | 11 +++++-- .../admin-web/src/config/systemSettings.ts | 32 +++++++++++++++++++ .../apps/backend/.env.example | 2 ++ .../apps/backend/app/core/config.py | 2 ++ .../backend/app/services/secret_service.py | 6 +++- .../app/services/voice_input_service.py | 8 ++--- .../apps/backend/tests/test_voice_input.py | 20 ++++++++++-- .../src/components/ChatComposer.vue | 23 +++++++++++-- ai_knowledge_base_v2/docker-compose.dev.yml | 2 ++ 10 files changed, 97 insertions(+), 11 deletions(-) diff --git a/ai_knowledge_base_v2/.env.example b/ai_knowledge_base_v2/.env.example index f23dc74..1f5e989 100644 --- a/ai_knowledge_base_v2/.env.example +++ b/ai_knowledge_base_v2/.env.example @@ -48,4 +48,6 @@ FRP_LOCAL_PORT=80 # 自定义域名,逗号分隔,如:qa.huiyushuyuan.cn FRP_CUSTOM_DOMAINS=qa.huiyushuyuan.cn ALIYUN_NLS_APP_KEY= +ALIYUN_NLS_ACCESS_KEY_ID= +ALIYUN_NLS_ACCESS_KEY_SECRET= ALIYUN_NLS_ENDPOINT=https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/asr diff --git a/ai_knowledge_base_v2/apps/admin-web/src/components/SystemConfigView.vue b/ai_knowledge_base_v2/apps/admin-web/src/components/SystemConfigView.vue index c295244..ccdc314 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/components/SystemConfigView.vue +++ b/ai_knowledge_base_v2/apps/admin-web/src/components/SystemConfigView.vue @@ -83,8 +83,15 @@ const groupBlueprints: Record = { }, { title: "语音输入", - description: "控制用户端麦克风入口和单次录音上限。", - keys: ["voice_input_enabled", "voice_max_duration_seconds"], + description: "控制用户端麦克风入口、录音上限和独立的阿里云语音凭证。", + keys: [ + "voice_input_enabled", + "voice_max_duration_seconds", + "aliyun_nls_app_key", + "aliyun_nls_access_key_id", + "aliyun_nls_access_key_secret", + "aliyun_nls_endpoint", + ], }, ], 外部服务: [ diff --git a/ai_knowledge_base_v2/apps/admin-web/src/config/systemSettings.ts b/ai_knowledge_base_v2/apps/admin-web/src/config/systemSettings.ts index fcc67c0..1aee55b 100644 --- a/ai_knowledge_base_v2/apps/admin-web/src/config/systemSettings.ts +++ b/ai_knowledge_base_v2/apps/admin-web/src/config/systemSettings.ts @@ -231,6 +231,38 @@ export const systemSettingSections: SystemSettingSection[] = [ max: 60, description: "达到设定时长后自动停止并转成文字,阿里云一句话识别上限为 60 秒。", }, + { + key: "aliyun_nls_app_key", + label: "阿里云语音 AppKey", + type: "text", + defaultValue: "", + placeholder: "请输入智能语音交互项目 AppKey", + description: "填写阿里云智能语音交互控制台中对应应用的 AppKey。", + }, + { + key: "aliyun_nls_access_key_id", + label: "语音服务 AccessKey ID", + type: "password", + defaultValue: "", + placeholder: "请单独填写语音服务 AccessKey ID", + description: "语音服务独立配置;如与短信使用同一账号,也需要在这里手动填写。", + }, + { + key: "aliyun_nls_access_key_secret", + label: "语音服务 AccessKey Secret", + type: "password", + defaultValue: "", + placeholder: "请单独填写语音服务 AccessKey Secret", + description: "加密保存且不会明文回显,不再从短信配置中读取。", + }, + { + key: "aliyun_nls_endpoint", + label: "阿里云语音 Endpoint", + type: "text", + defaultValue: "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/asr", + placeholder: "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/asr", + description: "一句话识别服务地址,一般保持默认值即可。", + }, ], }, { diff --git a/ai_knowledge_base_v2/apps/backend/.env.example b/ai_knowledge_base_v2/apps/backend/.env.example index 4b13dca..5c07d15 100644 --- a/ai_knowledge_base_v2/apps/backend/.env.example +++ b/ai_knowledge_base_v2/apps/backend/.env.example @@ -59,4 +59,6 @@ BOOTSTRAP_ADMIN_USERNAME=admin BOOTSTRAP_ADMIN_PASSWORD=admin123456 BOOTSTRAP_ADMIN_NAME=系统管理员 ALIYUN_NLS_APP_KEY= +ALIYUN_NLS_ACCESS_KEY_ID= +ALIYUN_NLS_ACCESS_KEY_SECRET= ALIYUN_NLS_ENDPOINT=https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/asr diff --git a/ai_knowledge_base_v2/apps/backend/app/core/config.py b/ai_knowledge_base_v2/apps/backend/app/core/config.py index 06ac7d2..964df60 100644 --- a/ai_knowledge_base_v2/apps/backend/app/core/config.py +++ b/ai_knowledge_base_v2/apps/backend/app/core/config.py @@ -49,6 +49,8 @@ class Settings(BaseSettings): aliyun_sms_template_param_key: str = "code" aliyun_sms_endpoint: str = "dysmsapi.aliyuncs.com" aliyun_nls_app_key: str = "" + aliyun_nls_access_key_id: str = "" + aliyun_nls_access_key_secret: str = "" aliyun_nls_endpoint: str = "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/asr" mock_rag_enabled: bool = False mock_model_enabled: bool = True diff --git a/ai_knowledge_base_v2/apps/backend/app/services/secret_service.py b/ai_knowledge_base_v2/apps/backend/app/services/secret_service.py index 54a67e6..80e68f3 100644 --- a/ai_knowledge_base_v2/apps/backend/app/services/secret_service.py +++ b/ai_knowledge_base_v2/apps/backend/app/services/secret_service.py @@ -10,7 +10,11 @@ from app.core.config import get_settings ENCRYPTED_PREFIX = "enc:v1:" MASKED_SECRET = "******" -SENSITIVE_CONFIG_KEYS = {"aliyun_sms_access_key_secret", "feishu_app_secret"} +SENSITIVE_CONFIG_KEYS = { + "aliyun_sms_access_key_secret", + "aliyun_nls_access_key_secret", + "feishu_app_secret", +} class SecretService: diff --git a/ai_knowledge_base_v2/apps/backend/app/services/voice_input_service.py b/ai_knowledge_base_v2/apps/backend/app/services/voice_input_service.py index 15bf0ac..87fd62a 100644 --- a/ai_knowledge_base_v2/apps/backend/app/services/voice_input_service.py +++ b/ai_knowledge_base_v2/apps/backend/app/services/voice_input_service.py @@ -105,15 +105,15 @@ def load_voice_config(db: Session) -> VoiceInputConfig: values = {row.config_key: row.config_value for row in rows} enabled = _bool(values.get("voice_input_enabled"), settings.voice_input_enabled) duration = _int(values.get("voice_max_duration_seconds"), settings.voice_max_duration_seconds, 5, 60) - access_key_id = str(values.get("aliyun_sms_access_key_id") or settings.aliyun_sms_access_key_id).strip() - encrypted_secret = str(values.get("aliyun_sms_access_key_secret") or settings.aliyun_sms_access_key_secret).strip() + access_key_id = str(values.get("aliyun_nls_access_key_id") or settings.aliyun_nls_access_key_id).strip() + encrypted_secret = str(values.get("aliyun_nls_access_key_secret") or settings.aliyun_nls_access_key_secret).strip() return VoiceInputConfig( enabled=enabled, max_duration_seconds=duration, - app_key=settings.aliyun_nls_app_key.strip(), + app_key=str(values.get("aliyun_nls_app_key") or settings.aliyun_nls_app_key).strip(), access_key_id=access_key_id, access_key_secret=SecretService.decrypt(encrypted_secret), - endpoint=settings.aliyun_nls_endpoint.strip(), + endpoint=str(values.get("aliyun_nls_endpoint") or settings.aliyun_nls_endpoint).strip(), ) diff --git a/ai_knowledge_base_v2/apps/backend/tests/test_voice_input.py b/ai_knowledge_base_v2/apps/backend/tests/test_voice_input.py index 97fb4a8..6171adb 100644 --- a/ai_knowledge_base_v2/apps/backend/tests/test_voice_input.py +++ b/ai_knowledge_base_v2/apps/backend/tests/test_voice_input.py @@ -24,9 +24,9 @@ def settings(**overrides): "voice_input_enabled": False, "voice_max_duration_seconds": 60, "aliyun_nls_app_key": "app-key", + "aliyun_nls_access_key_id": "voice-access-id", + "aliyun_nls_access_key_secret": "voice-access-secret", "aliyun_nls_endpoint": "https://nls.example/asr", - "aliyun_sms_access_key_id": "access-id", - "aliyun_sms_access_key_secret": "access-secret", } values.update(overrides) return SimpleNamespace(**values) @@ -50,6 +50,22 @@ def test_transcription_endpoint_is_rejected_when_switch_is_off(monkeypatch): assert exc.value.status_code == 404 +def test_voice_credentials_are_loaded_independently_from_sms(monkeypatch): + monkeypatch.setattr("app.services.voice_input_service.get_settings", lambda: settings()) + with database() as db: + db.add_all([ + SystemConfig(config_key="aliyun_sms_access_key_id", config_value="sms-id"), + SystemConfig(config_key="aliyun_nls_app_key", config_value="db-app-key"), + SystemConfig(config_key="aliyun_nls_access_key_id", config_value="db-voice-id"), + SystemConfig(config_key="aliyun_nls_access_key_secret", config_value="db-voice-secret"), + ]) + db.commit() + config = load_voice_config(db) + assert config.app_key == "db-app-key" + assert config.access_key_id == "db-voice-id" + assert config.access_key_secret == "db-voice-secret" + + def test_transcription_returns_provider_text(monkeypatch): monkeypatch.setattr("app.services.voice_input_service.get_settings", lambda: settings(voice_input_enabled=True)) monkeypatch.setattr("app.services.voice_input_service._normalize_audio", lambda *_: b"wav") diff --git a/ai_knowledge_base_v2/apps/user-client/src/components/ChatComposer.vue b/ai_knowledge_base_v2/apps/user-client/src/components/ChatComposer.vue index 7b0af86..acf84bf 100644 --- a/ai_knowledge_base_v2/apps/user-client/src/components/ChatComposer.vue +++ b/ai_knowledge_base_v2/apps/user-client/src/components/ChatComposer.vue @@ -1,6 +1,6 @@