Compare commits

..

2 Commits

Author SHA1 Message Date
xiaoxia 8612e183ad fix(config): 运行时读取 SessionLocal 修复后台配置接口500 #2246 (#2248)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 1m4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m7s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m22s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 58s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m13s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m23s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m37s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m27s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m19s
AI Code Review / AI Code Review (pull_request) Successful in 7m17s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m13s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m43s
CI/CD Pipeline / Integration Tests (push) Successful in 11m37s
CI/CD Pipeline / Validate - Style (push) Successful in 13m46s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 15m3s
CI/CD Pipeline / Unit Tests (push) Failing after 18m32s
CI/CD Pipeline / Validate - Security (push) Successful in 31m38s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 25m44s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
## 背景
#2247 合并部署到 staging 后,`GET /api/v1/admin/ditto-emotion/config` 返回 500。

## 根因
`system_config_service.py` 在**模块导入时**执行 `from ...session import SessionLocal`,此刻 `SessionLocal` 还是 `None`;之后 `initialize_database()` 才给它赋值。import 时绑定把旧的 `None` 固化,服务 fallback 取到的始终是 None,抛出 `RuntimeError(数据库会话工厂未初始化)`。

## 修复
- 改为 `from packages.adapters.sqlalchemy_impl import session as db_session`,运行时读取 `db_session.SessionLocal`。
- 补 2 个回归用例:模拟「导入时 None、启动后才赋值」的真实启动顺序;无工厂时抛 RuntimeError。

## 测试
- `tests/unit/test_system_config.py`:**29 passed**(原 27 + 新增 2)。
- black/isort/ruff 通过。

关联 #2246。
2026-10-08 23:04:59 +08:00
xiaoxia c4b4a1e9c0 feat(admin): Ditto 数字人表情后台配置页 #2246 (#2247)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 1m0s
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 48s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m9s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m51s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m12s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m2s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m19s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m29s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m17s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m12s
AI Code Review / AI Code Review (pull_request) Successful in 7m19s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m39s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m54s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m13s
CI/CD Pipeline / Integration Tests (push) Successful in 11m34s
CI/CD Pipeline / Validate - Style (push) Successful in 12m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 14m46s
CI/CD Pipeline / Unit Tests (push) Failing after 21m58s
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
## 背景

关联 #2246。Ditto LLM 情绪分析(#2245)已上线,但 5 项配置只能靠环境变量/改代码调整。本 PR 提供后台配置页,不改代码、不发版即可调优,保存后立即生效。

## 改动文件

### 后端
- `packages/domain/system_setting.py`(新):SystemSetting 实体 + bool/int/float/string/json 序列化反序列化
- `packages/adapters/sqlalchemy_impl/models.py`:新增 SystemSettingModel
- `packages/adapters/sqlalchemy_impl/system_setting_repository.py`(新):Repository(get/list/upsert/delete)
- `packages/application/system_config_service.py`(新):SystemConfigService 进程内缓存,set_config 后失效并联动 reset Ditto 单例
- `packages/application/ditto_emotion_service.py`:配置优先读 system_config、env 兜底;新增 reset_ditto_emotion_service(含 LRU cache_clear)
- `packages/application/ditto_service.py`:blend_frames 同上;新增 reset_ditto_client
- `apps/api/app/api/routes/admin/__init__.py`、`admin/ditto_emotion.py`(新):GET/PUT `/config`、POST `/config/test`,X-API-Key 鉴权 + 5 项白名单
- `apps/api/app/api/router.py`:注册新路由
- `alembic/versions/106_system_settings.py`(新):正式建表(IF NOT EXISTS,staging 手工表幂等)

### 前端
- `apps/web/src/pages/admin/DittoEmotionConfig.tsx`(新):Key 弹窗(localStorage)、5 表单项、保存/测试/重置默认
- `apps/web/src/api/admin/dittoEmotion.ts`(新):独立 axios admin client(X-API-Key,不走 Bearer token)
- `apps/web/src/router/appRoutes.tsx`:`/app/admin` 重定向到配置页;未动用户端 `/app/*`

## 测试
- 新增 `tests/unit/test_system_config.py`:**27 passed**(默认值/DB覆盖/白名单拒绝/单例 reset/校验)
- `tests/unit/test_viral_video_v8_bugs.py`:**39 passed**(无回归)
- 全量 `tests/unit`:16704 passed,仅 2 个 storage `sign_url(slash_safe=True)` 失败——本地容器 oss2 版本高于 requirements 锁定版导致,与本 PR 无关,CI 固定版本不受影响
- 前端 `tsc --noEmit`:零错误

## 截图

**1. 首次进入:输入后台 X-API-Key(仅存本机 localStorage)**

![X-API-Key 弹窗](https://git.xiaoxiajianji.com/attachments/4b8025ac-4b6d-4ba3-9ee5-a12d88d6a0e1)

**2. 配置页:5 项配置 + 立即生效 + 配置测试**

![配置页](https://git.xiaoxiajianji.com/attachments/8c1a6d2c-0ac3-415a-913d-838f8c125806)

## 严禁项确认
未改 lipsync_ditto 调用流程 / ditto 核心逻辑 / generate 重试逻辑 / config 字段名默认值;未建新业务表(用 system_settings);未动用户端;API Key 不硬编码;worker 不直接 HTTP 读配置。
2026-10-08 22:31:42 +08:00
2 changed files with 35 additions and 2 deletions
@@ -12,7 +12,7 @@ import logging
import threading
from typing import Any
from packages.adapters.sqlalchemy_impl.session import SessionLocal
from packages.adapters.sqlalchemy_impl import session as db_session
from packages.adapters.sqlalchemy_impl.system_setting_repository import (
SQLAlchemySystemSettingRepository,
)
@@ -35,7 +35,9 @@ class SystemConfigService:
# ── 会话 ─────────────────────────────────────────────────────
def _get_session_factory(self):
factory = self._session_factory or SessionLocal
# 必须运行时读取模块属性:模块导入时 SessionLocal 还是 None,
# initialize_database() 之后才被赋值,import 时绑定会拿到旧值。
factory = self._session_factory or db_session.SessionLocal
if factory is None:
raise RuntimeError("数据库会话工厂未初始化")
return factory
+31
View File
@@ -91,6 +91,37 @@ class TestConfigService:
def test_default_when_missing(self, config_service):
assert config_service.get_config("not_exist", "fallback") == "fallback"
def test_uses_module_session_factory_assigned_after_import(self):
# 回归:模块导入时 session.SessionLocal 为 None,initialize_database()
# 之后才赋值;服务必须运行时读取模块属性,而不是 import 时绑定旧值。
from packages.adapters.sqlalchemy_impl import session as db_session
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine)
writer = SystemConfigService(session_factory=factory)
writer.set_config("late_key", 7, setting_type=SETTING_TYPE_INT)
reader = SystemConfigService() # 不注入工厂,依赖模块级 SessionLocal
old = db_session.SessionLocal
try:
db_session.SessionLocal = factory
assert reader.get_config("late_key", 0) == 7
finally:
db_session.SessionLocal = old
def test_raises_when_no_session_factory(self):
from packages.adapters.sqlalchemy_impl import session as db_session
svc = SystemConfigService()
old = db_session.SessionLocal
try:
db_session.SessionLocal = None
with pytest.raises(RuntimeError):
svc.get_config("anything", 1)
finally:
db_session.SessionLocal = old
def test_db_overrides_default(self, config_service):
config_service.set_config("k", 20, setting_type=SETTING_TYPE_INT)
# 再次读取应命中 DB 值,而非传入的默认