Просмотр исходного кода

feat:修复README.md文档与一键启动代码问题

yangxiaolong 1 месяц назад
Родитель
Сommit
cf73aa2989

+ 28 - 11
README.md

@@ -28,7 +28,7 @@
 - 前端:Vue 3、TypeScript、Vite、pnpm workspace。
 - 依赖与质量:uv、pytest、Ruff、mypy、vue-tsc。
 
-第二阶段使用独立的 `insurance_s2_*` 数据库。压缩包内的第二阶段 SQL 已包含从第一阶段继承后的业务数据,但只会重建第二阶段数据库。
+第二阶段使用独立的 `insurance_s2_*` 数据库。`database/mysql.sql` 已包含从第一阶段继承后的业务数据,但只会重建第二阶段数据库。
 
 ## 首次初始化
 
@@ -36,31 +36,48 @@
 
 如果尚未准备 MySQL 和 Redis,请先阅读 [Docker 基础环境使用说明](database/Docker基础环境使用说明.md),并使用其中的 `database/compose.yaml`。
 
-项目只读取 `backend/.env`。确认其中的 MySQL、Redis、Milvus、模型服务和 LangSmith 配置后,在项目根目录按顺序运行:
+项目只读取 `backend/.env`。确认其中的 MySQL、Redis、Milvus、模型服务和 LangSmith 配置后,在项目根目录按下面的顺序执行。
 
-Windows PowerShell:
+### 1. 安装项目依赖
 
-```powershell
+Windows PowerShell、macOS 和 Linux 使用相同命令:
+
+```bash
 uv sync --project backend --locked
 pnpm --dir frontend install --frozen-lockfile
+```
+
+### 2. 初始化 MySQL 数据
+
+MySQL 数据来自 `database/mysql.sql`,其中已经包含第一阶段继承数据和第二阶段所需业务数据。下面的命令会自动导入 `insurance_s2_core`、`insurance_s2_agent` 和 `insurance_s2_analytics`。
 
+Windows PowerShell:
+
+```powershell
 .\scripts\init-databases.ps1
-.\scripts\seed-knowledge.ps1
 ```
 
 macOS / Linux:
 
 ```bash
-uv sync --project backend --locked
-pnpm --dir frontend install --frozen-lockfile
-
 ./scripts/init-databases.sh
-./scripts/seed-knowledge.sh
 ```
 
-以上命令依次完成后端依赖安装、前端依赖安装、MySQL 数据导入和 Milvus 知识库初始化。
+SQL 已经包含完整表结构和初始数据,因此首次初始化不需要再执行迁移、种子或阶段继承命令。
+
+### 3. 初始化 Milvus 知识库
+
+Windows PowerShell:
 
-MySQL 数据来自 `database/mysql.sql`,其中已经包含第一阶段继承数据和第二阶段所需业务数据。`init-databases` 脚本会自动导入 `insurance_s2_core`、`insurance_s2_agent` 和 `insurance_s2_analytics`,因此首次初始化不需要再单独执行迁移、种子或阶段继承命令。
+```powershell
+.\scripts\seed-knowledge.ps1
+```
+
+macOS / Linux:
+
+```bash
+./scripts/seed-knowledge.sh
+```
 
 ## 启动
 

+ 16 - 2
backend/src/zbt/infrastructure/milvus/knowledge_index.py

@@ -26,6 +26,8 @@ class MilvusKnowledgeIndex:
         self._embedding_model: Any | None = None
         self._client_lock = Lock()
         self._embedding_model_lock = Lock()
+        self._collection_lock = Lock()
+        self._collection_ready = False
 
     def warmup(self) -> None:
         """预连接 Milvus,并在后台把本地 BGE-M3 权重装入内存。"""
@@ -164,10 +166,22 @@ class MilvusKnowledgeIndex:
         return cast(list[list[float]], raw_vectors)
 
     def _ensure_collection(self, client: MilvusClient) -> None:
-        """确保知识集合和向量索引存在;已存在时不重复创建。"""
+        """确保知识集合存在,并已加载到 QueryNode。"""
 
-        if client.has_collection(self._collection_name):
+        if self._collection_ready:
             return
+        with self._collection_lock:
+            if self._collection_ready:
+                return
+            if not client.has_collection(self._collection_name):
+                self._create_collection(client)
+            client.load_collection(
+                collection_name=self._collection_name,
+                timeout=30.0,
+            )
+            self._collection_ready = True
+
+    def _create_collection(self, client: MilvusClient) -> None:
         schema = MilvusClient.create_schema(auto_id=False, enable_dynamic_field=False)
         schema.add_field(
             field_name="id",

+ 15 - 1
backend/src/zbt/infrastructure/milvus/memory_index.py

@@ -25,6 +25,8 @@ class MilvusCustomerMemoryIndex(CustomerMemoryIndex):
         self._embedding_model: Any | None = None
         self._client_lock = Lock()
         self._embedding_model_lock = Lock()
+        self._collection_lock = Lock()
+        self._collection_ready = False
 
     def upsert(self, memory: CustomerMemory) -> None:
         try:
@@ -134,8 +136,20 @@ class MilvusCustomerMemoryIndex(CustomerMemoryIndex):
         return cast(list[list[float]], raw_vectors)
 
     def _ensure_collection(self, client: MilvusClient) -> None:
-        if client.has_collection(self._collection_name):
+        if self._collection_ready:
             return
+        with self._collection_lock:
+            if self._collection_ready:
+                return
+            if not client.has_collection(self._collection_name):
+                self._create_collection(client)
+            client.load_collection(
+                collection_name=self._collection_name,
+                timeout=30.0,
+            )
+            self._collection_ready = True
+
+    def _create_collection(self, client: MilvusClient) -> None:
         schema = MilvusClient.create_schema(
             auto_id=False,
             enable_dynamic_field=False,

+ 2 - 0
database/Docker基础环境使用说明.md

@@ -35,6 +35,8 @@ docker compose -f database/compose.yaml ps
 
 当 MySQL 和 Redis 的状态均为 `healthy` 后,再执行根目录 `README.md` 中的首次初始化命令。
 
+`docker compose ps` 还应显示 MySQL 的 `0.0.0.0:3306->3306/tcp` 端口映射。如果没有该映射,通常是本机已有 MySQL 占用了 3306。请先停止本机原有 MySQL 服务,再重新执行上面的 Docker 启动命令;不要让本机 MySQL 与 Docker MySQL 同时占用 3306。
+
 ## 4. backend/.env 配置
 
 使用本 compose 文件时,`backend/.env` 中的公共连接配置应与下面保持一致:

+ 1 - 1
frontend/apps/admin/package.json

@@ -4,7 +4,7 @@
   "version": "0.1.0",
   "type": "module",
   "scripts": {
-    "dev": "vite --host 127.0.0.1 --port 5174",
+    "dev": "vite --host 127.0.0.1 --port 5174 --strictPort",
     "build": "vue-tsc -b && vite build",
     "typecheck": "vue-tsc -b"
   },

+ 1 - 1
frontend/apps/h5/package.json

@@ -4,7 +4,7 @@
   "version": "0.1.0",
   "type": "module",
   "scripts": {
-    "dev": "vite --host 127.0.0.1 --port 5173",
+    "dev": "vite --host 127.0.0.1 --port 5173 --strictPort",
     "build": "vue-tsc -b && vite build",
     "typecheck": "vue-tsc -b"
   },

+ 1 - 1
frontend/package.json

@@ -6,7 +6,7 @@
     "node": ">=22 <23"
   },
   "scripts": {
-    "dev": "pnpm --parallel --filter './apps/*' dev",
+    "dev": "pnpm --parallel --filter \"./apps/*\" dev",
     "dev:h5": "pnpm --filter @zbt/h5 dev",
     "dev:admin": "pnpm --filter @zbt/admin dev",
     "build": "pnpm --workspace-concurrency=1 --recursive build",

+ 8 - 3
scripts/dev.ps1

@@ -2,7 +2,11 @@
 param(
     # 仅用于自动化验证;正常启动时保持 0,由 Ctrl+C 结束。
     [ValidateRange(0, 3600)]
-    [int]$RunForSeconds = 0
+    [int]$RunForSeconds = 0,
+
+    # 首次启动可能需要加载本地嵌入模型,因此预留充足的冷启动时间。
+    [ValidateRange(10, 600)]
+    [int]$StartupTimeoutSeconds = 120
 )
 
 $ErrorActionPreference = "Stop"
@@ -109,7 +113,7 @@ function Wait-ForDevelopmentServices {
     param(
         [System.Diagnostics.Process]$FrontendProcess,
         [System.Diagnostics.Process]$BackendProcess,
-        [int]$TimeoutSeconds = 45
+        [int]$TimeoutSeconds = 120
     )
 
     $Deadline = (Get-Date).AddSeconds($TimeoutSeconds)
@@ -218,7 +222,8 @@ try {
 
     Wait-ForDevelopmentServices `
         -FrontendProcess $FrontendProcess `
-        -BackendProcess $BackendProcess
+        -BackendProcess $BackendProcess `
+        -TimeoutSeconds $StartupTimeoutSeconds
 
     Write-Host ""
     Write-Host "智保通开发服务已全部启动:" -ForegroundColor Green

+ 12 - 2
scripts/dev.sh

@@ -6,6 +6,7 @@ frontend_pid=""
 backend_pid=""
 cleanup_done=0
 run_for_seconds="${ZBT_DEV_RUN_SECONDS:-0}"
+startup_timeout_seconds="${ZBT_DEV_STARTUP_TIMEOUT_SECONDS:-120}"
 
 die() {
     echo "错误:$*" >&2
@@ -96,7 +97,7 @@ http_ready() {
 }
 
 wait_for_services() {
-    local deadline=$((SECONDS + 45))
+    local deadline=$((SECONDS + startup_timeout_seconds))
 
     while (( SECONDS < deadline )); do
         kill -0 "$frontend_pid" 2>/dev/null || \
@@ -113,7 +114,7 @@ wait_for_services() {
         sleep 0.3
     done
 
-    die "开发服务在 45 秒内未全部就绪,请检查上方启动日志。"
+    die "开发服务在 ${startup_timeout_seconds} 秒内未全部就绪,请检查上方启动日志。"
 }
 
 case "$run_for_seconds" in
@@ -122,6 +123,15 @@ case "$run_for_seconds" in
         ;;
 esac
 
+case "$startup_timeout_seconds" in
+    ''|*[!0-9]*)
+        die "ZBT_DEV_STARTUP_TIMEOUT_SECONDS 必须是正整数。"
+        ;;
+    0)
+        die "ZBT_DEV_STARTUP_TIMEOUT_SECONDS 必须大于 0。"
+        ;;
+esac
+
 trap cleanup EXIT
 trap 'exit 130' INT
 trap 'exit 143' TERM

+ 1 - 1
scripts/test.ps1

@@ -8,7 +8,7 @@ try {
     if ($LASTEXITCODE -ne 0) { throw "测试失败。" }
     uv run ruff check .
     if ($LASTEXITCODE -ne 0) { throw "Ruff 检查失败。" }
-    uv run mypy app
+    uv run mypy src/zbt
     if ($LASTEXITCODE -ne 0) { throw "mypy 检查失败。" }
 }
 finally {