# 模块独立部署与单机版方案 > **文档版本:** v1.0 > **创建日期:** 2025-11-06 > **最后更新:** 2025-11-06 > **文档状态:** 架构设计 > **作者:** 技术架构师 --- ## 📋 核心问题 1. **如何实现每个模块独立部署?** - 模块依赖平台层和通用能力层,如何独立? - 如何变成独立产品销售? - 如何实现本地化部署? 2. **如何开发单机版?** - 如何利用Electron框架? - 如何复用现有代码? - 架构如何设计? --- ## 🎯 Part 1:模块独立部署方案 ### 核心理念:独立部署 ≠ 完全孤立 **关键洞察:** ``` 独立部署的真正含义: ✅ 可以单独打包 ✅ 可以单独运行 ✅ 可以单独销售 ✅ 可以单独升级 但不是: ❌ 完全不依赖其他代码 ❌ 完全不共享基础设施 ``` **类比:** ``` 就像一个独立的手机App: - 它依赖操作系统(Android/iOS) - 它依赖系统API(摄像头、定位等) - 但它可以独立下载、安装、运行、卸载 模块独立部署也是一样: - 它依赖平台层(用户认证、存储等) - 它依赖通用能力层(LLM网关、文档处理等) - 但它可以独立打包、部署、销售 ``` --- ## 📦 方案一:完整打包(推荐用于独立产品) ### 适用场景 - ✅ 模块作为独立产品销售(如审稿系统) - ✅ 客户要求完全本地化部署 - ✅ 不依赖其他模块或平台 ### 核心思路 **将依赖的平台层和通用能力层一起打包** ``` 审稿系统独立产品包: ┌─────────────────────────────────────────┐ │ RVW审稿系统独立产品 │ │ │ │ ┌────────────────────────────────────┐ │ │ │ 业务模块层 │ │ │ │ RVW(稿件审查) │ │ │ └────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────┐ │ │ │ 通用能力层(必需部分) │ │ │ │ - LLM网关 │ │ │ │ - 文档处理引擎 │ │ │ └────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────┐ │ │ │ 平台基础层(必需部分) │ │ │ │ - 用户认证(简化版) │ │ │ │ - 存储服务 │ │ │ │ - 监控日志 │ │ │ └────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────┐ │ │ │ 独立数据库 │ │ │ │ PostgreSQL(Docker) │ │ │ └────────────────────────────────────┘ │ └─────────────────────────────────────────┘ ``` --- ### 技术实现 #### Step 1: 代码组织(Monorepo) ``` AIclinicalresearch/ ├── packages/ # Monorepo包管理 │ │ │ ├── platform-core/ # 平台核心(可复用) │ │ ├── auth/ # 用户认证 │ │ ├── storage/ # 存储服务 │ │ ├── monitoring/ # 监控日志 │ │ └── index.ts │ │ │ ├── capabilities-core/ # 通用能力核心(可复用) │ │ ├── llm-gateway/ # LLM网关 │ │ ├── document-processor/ # 文档处理 │ │ ├── rag-engine/ # RAG引擎 │ │ └── index.ts │ │ │ ├── module-aia/ # AI问答模块 │ │ ├── backend/ │ │ ├── frontend/ │ │ └── package.json │ │ │ ├── module-asl/ # AI文献模块 │ │ ├── backend/ │ │ ├── frontend/ │ │ └── package.json │ │ │ ├── module-review/ # 审稿系统模块 │ │ ├── backend/ │ │ ├── frontend/ │ │ └── package.json │ │ │ └── ... │ ├── products/ # 独立产品打包 │ │ │ ├── review-system/ # 审稿系统独立产品 ⭐ │ │ ├── docker-compose.yml │ │ ├── deploy.sh │ │ ├── README.md │ │ └── package.json # 依赖关系 │ │ │ ├── literature-system/ # AI文献独立产品 ⭐ │ │ └── ... │ │ │ └── full-platform/ # 完整平台 │ └── ... │ └── ... ``` --- #### Step 2: 依赖管理(package.json) **审稿系统独立产品的依赖:** ```json // products/review-system/package.json { "name": "@yizhengxun/review-system", "version": "1.0.0", "private": true, "dependencies": { // 平台核心(必需) "@yizhengxun/platform-core": "workspace:*", // 通用能力(必需) "@yizhengxun/capabilities-core": "workspace:*", // 业务模块 "@yizhengxun/module-review": "workspace:*" }, "scripts": { "build": "node build.js", "deploy": "sh deploy.sh" } } ``` **平台核心的选择性导出:** ```typescript // packages/platform-core/index.ts // 完整导出(用于完整平台) export * from './auth'; export * from './storage'; export * from './monitoring'; export * from './notification'; // 精简导出(用于独立产品) export { // 只导出必需的认证功能 authMiddleware, jwtService } from './auth'; export { // 只导出必需的存储功能 uploadFile, downloadFile } from './storage'; export { // 只导出必需的监控功能 logError, logAudit } from './monitoring'; ``` --- #### Step 3: 打包脚本 **审稿系统独立打包:** ```javascript // products/review-system/build.js const { build } = require('esbuild'); const { dependencies } = require('./package.json'); async function buildReviewSystem() { console.log('构建审稿系统独立产品...'); // 1. 构建后端 await build({ entryPoints: ['../../packages/module-review/backend/src/index.ts'], bundle: true, platform: 'node', target: 'node18', outfile: 'dist/backend/index.js', external: ['pg', 'fastify', 'prisma'], // 不打包这些大库 // 自动包含依赖 plugins: [ // 自动解析workspace依赖 resolveWorkspaceDependencies() ] }); // 2. 构建前端 await build({ entryPoints: ['../../packages/module-review/frontend/src/main.tsx'], bundle: true, platform: 'browser', outfile: 'dist/frontend/index.js', loader: { '.tsx': 'tsx' } }); // 3. 复制必要文件 copyFiles([ 'docker-compose.yml', 'README.md', 'deploy.sh' ]); console.log('构建完成!'); } buildReviewSystem(); ``` --- #### Step 4: Docker部署配置 **审稿系统独立部署:** ```yaml # products/review-system/docker-compose.yml version: '3.8' services: # PostgreSQL数据库(独立) postgres: image: postgres:15-alpine container_name: review-system-postgres environment: POSTGRES_DB: review_system POSTGRES_USER: review POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data networks: - review-network # 后端服务(包含平台层和通用能力层) backend: build: context: ./dist/backend container_name: review-system-backend environment: DATABASE_URL: postgresql://review:${DB_PASSWORD}@postgres:5432/review_system LLM_API_KEY: ${LLM_API_KEY} depends_on: - postgres networks: - review-network # 前端服务 frontend: build: context: ./dist/frontend container_name: review-system-frontend ports: - "80:80" depends_on: - backend networks: - review-network volumes: postgres_data: networks: review-network: driver: bridge ``` --- #### Step 5: 一键部署脚本 ```bash #!/bin/bash # products/review-system/deploy.sh echo "======================================" echo " 审稿系统独立部署脚本" echo "======================================" # 1. 检查Docker if ! command -v docker &> /dev/null; then echo "错误:未安装Docker" exit 1 fi # 2. 设置环境变量 read -p "请输入数据库密码: " DB_PASSWORD read -p "请输入LLM API密钥: " LLM_API_KEY export DB_PASSWORD export LLM_API_KEY # 3. 启动服务 docker-compose up -d # 4. 等待服务启动 echo "等待服务启动..." sleep 10 # 5. 初始化数据库 docker exec review-system-backend npx prisma migrate deploy # 6. 创建默认管理员 docker exec review-system-backend node scripts/create-admin.js echo "======================================" echo " 部署完成!" echo " 访问地址:http://localhost" echo " 管理员账号:admin@review.com" echo " 密码:admin123(请及时修改)" echo "======================================" ``` --- ### 关键技术点 #### 1. 平台层的精简和适配 **完整平台的用户认证(复杂):** ```typescript // 完整平台 - 多租户支持 - RBAC权限控制 - SSO单点登录 - 第三方登录(微信、企业微信等) - 复杂的权限树 ``` **审稿系统的用户认证(简化):** ```typescript // 审稿系统独立产品 - 简化的用户认证(邮箱+密码) - 简单的角色(管理员、编辑、审稿人) - 基于JWT的Token认证 - 无SSO、无多租户 ``` **实现方式:** ```typescript // packages/platform-core/auth/simple-auth.ts // 为独立产品提供简化版认证 export class SimpleAuthService { // 只保留核心功能 async login(email: string, password: string) { } async register(email: string, password: string) { } async verifyToken(token: string) { } async logout(userId: string) { } // 移除复杂功能 // ❌ async loginWithWeChat() // ❌ async setupSSO() // ❌ async multiTenantCheck() } ``` --- #### 2. 通用能力层的精简和适配 **完整平台的LLM网关(复杂):** ```typescript // 完整平台 - 支持10+种模型 - Feature Flag控制 - 配额管理 - 成本分析 - A/B测试 ``` **审稿系统的LLM网关(简化):** ```typescript // 审稿系统独立产品 - 只支持1-2种模型(DeepSeek + Claude) - 简单的配额检查 - 无Feature Flag - 无成本分析 ``` **实现方式:** ```typescript // packages/capabilities-core/llm-gateway/simple-llm.ts export class SimpleLLMGateway { // 只保留核心功能 async chat(params: ChatParams) { } async checkQuota(userId: string) { } // 移除复杂功能 // ❌ async getCostStats() // ❌ async selectModelByVersion() // ❌ async runABTest() } ``` --- #### 3. 数据库的隔离 **完整平台的数据库(复杂):** ```sql -- 平台层Schema platform_schema.users platform_schema.tenants platform_schema.feature_flags -- ... -- 所有模块Schema aia_schema.* asl_schema.* review_schema.* -- ... ``` **审稿系统独立产品的数据库(简化):** ```sql -- 只包含必需的表 CREATE DATABASE review_system; -- 简化的用户表 CREATE TABLE users ( id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, role VARCHAR(50) NOT NULL, -- admin/editor/reviewer created_at TIMESTAMP DEFAULT NOW() ); -- 审稿系统的业务表 CREATE TABLE review_tasks (...); CREATE TABLE review_journals (...); CREATE TABLE review_reviewers (...); -- ... ``` --- ### 独立产品的优势 | 优势 | 说明 | |------|------| | **独立销售** | 可以单独定价、单独销售 | | **独立部署** | 客户可以本地化部署,数据完全隔离 | | **独立升级** | 不影响其他模块 | | **轻量化** | 只包含必需功能,包体积小 | | **定制化** | 可以针对特定客户定制 | --- ## 📦 方案二:共享服务(推荐用于平台内模块) ### 适用场景 - ✅ 同一客户购买多个模块 - ✅ 云端SaaS部署 - ✅ 模块之间需要共享用户和数据 ### 核心思路 **平台层和通用能力层作为共享服务** ``` 完整平台架构(微服务版): ┌─────────────────────────────────────────────────────────┐ │ API网关(Kong/Traefik) │ │ 路由:/aia/* /asl/* /review/* │ └─────────────────────────────────────────────────────────┘ │ │ │ ↓ ↓ ↓ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ AIA模块服务 │ │ ASL模块服务 │ │ RVW模块服务 │ │ (独立部署) │ │ (独立部署) │ │ (独立部署) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └──────────────┴──────────────┘ ↓ ┌───────────────────────────┐ │ 共享服务(平台层) │ │ - 用户认证服务 │ │ - 存储服务 │ │ - 监控服务 │ └───────────────────────────┘ ↓ ┌───────────────────────────┐ │ 共享服务(通用能力层) │ │ - LLM网关服务 │ │ - 文档处理服务 │ │ - RAG引擎服务 │ └───────────────────────────┘ ↓ ┌───────────────────────────┐ │ 共享数据库 │ │ - PostgreSQL (多Schema) │ └───────────────────────────┘ ``` --- ### 技术实现 #### Step 1: 服务拆分 **平台基础服务(独立部署):** ```yaml # services/platform/docker-compose.yml services: # 用户认证服务 auth-service: image: yizhengxun/auth-service:latest ports: - "3001:3000" environment: DATABASE_URL: ${DATABASE_URL} JWT_SECRET: ${JWT_SECRET} # 存储服务 storage-service: image: yizhengxun/storage-service:latest ports: - "3002:3000" environment: OSS_ENDPOINT: ${OSS_ENDPOINT} # 监控服务 monitoring-service: image: yizhengxun/monitoring-service:latest ports: - "3003:3000" ``` **通用能力服务(独立部署):** ```yaml # services/capabilities/docker-compose.yml services: # LLM网关服务 llm-gateway: image: yizhengxun/llm-gateway:latest ports: - "3010:3000" environment: DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} QWEN_API_KEY: ${QWEN_API_KEY} # 文档处理服务 document-processor: image: yizhengxun/document-processor:latest ports: - "3011:3000" # RAG引擎服务 rag-engine: image: yizhengxun/rag-engine:latest ports: - "3012:3000" ``` **业务模块服务(独立部署):** ```yaml # services/modules/review-system/docker-compose.yml services: review-system: image: yizhengxun/review-system:latest ports: - "4001:3000" environment: # 依赖共享服务 AUTH_SERVICE_URL: http://auth-service:3000 STORAGE_SERVICE_URL: http://storage-service:3000 LLM_GATEWAY_URL: http://llm-gateway:3000 DOCUMENT_PROCESSOR_URL: http://document-processor:3000 # 自己的数据库Schema DATABASE_URL: postgresql://user:pass@postgres:5432/platform?schema=review_schema ``` --- #### Step 2: API网关配置 **Kong API网关配置:** ```yaml # api-gateway/kong.yml _format_version: "3.0" services: # 用户认证服务 - name: auth-service url: http://auth-service:3000 routes: - name: auth-routes paths: - /api/auth # 审稿系统模块 - name: review-system url: http://review-system:3000 routes: - name: review-routes paths: - /api/review plugins: - name: jwt config: secret_is_base64: false key_claim_name: kid # AI文献模块 - name: literature-system url: http://literature-system:3000 routes: - name: literature-routes paths: - /api/literature plugins: - name: jwt ``` --- #### Step 3: 服务间通信 **业务模块调用共享服务:** ```typescript // packages/module-review/backend/src/services/review.service.ts import { AuthClient } from '@yizhengxun/platform-core/clients'; import { LLMClient } from '@yizhengxun/capabilities-core/clients'; export class ReviewService { private authClient: AuthClient; private llmClient: LLMClient; constructor() { // 连接到共享服务 this.authClient = new AuthClient({ baseURL: process.env.AUTH_SERVICE_URL }); this.llmClient = new LLMClient({ baseURL: process.env.LLM_GATEWAY_URL }); } async createReviewTask(userId: string, file: File) { // 1. 验证用户(调用共享认证服务) const user = await this.authClient.verifyUser(userId); // 2. 上传文件(调用共享存储服务) const fileUrl = await this.storageClient.upload(file); // 3. AI分析(调用共享LLM网关) const analysis = await this.llmClient.chat({ messages: [{ role: 'user', content: `分析这篇稿件: ${fileUrl}` }] }); // 4. 保存到自己的数据库 const task = await prisma.reviewTask.create({ data: { userId, fileUrl, analysis: analysis.content } }); return task; } } ``` --- ### 模块独立部署的步骤 **1. 部署共享服务(一次性):** ```bash # 部署平台基础服务 cd services/platform docker-compose up -d # 部署通用能力服务 cd services/capabilities docker-compose up -d # 部署API网关 cd services/api-gateway docker-compose up -d ``` **2. 部署业务模块(按需):** ```bash # 只部署审稿系统模块 cd services/modules/review-system docker-compose up -d # 客户A购买了审稿系统,只需部署这一个模块 # 其他模块不需要部署 ``` **3. 新客户购买其他模块:** ```bash # 客户B购买了AI文献模块,再部署这个模块 cd services/modules/literature-system docker-compose up -d # 共享服务已经部署,不需要重复部署 # 只需增加新的业务模块 ``` --- ## 🖥️ Part 2:Electron单机版方案 ### 为什么需要单机版? **核心需求:** 1. **数据隐私**:医生个人数据不能上传云端 2. **离线使用**:无需网络连接 3. **便携性**:可以在任何电脑上安装使用 4. **独立运行**:不依赖外部服务器 **目标用户:** - 个人医生 - 数据敏感的研究者 - 无网络环境的场景 --- ### Electron架构与现有代码的对应关系 #### 现有架构(云端版) ``` ┌─────────────────────────────────────────┐ │ 浏览器 (Chrome) │ │ ┌───────────────────────────────────┐ │ │ │ 前端 (React + Vite) │ │ │ │ http://localhost:5173 │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘ ↓ HTTP请求 ┌─────────────────────────────────────────┐ │ 后端 (Node.js + Fastify) │ │ http://localhost:3001 │ │ ┌───────────────────────────────────┐ │ │ │ API路由、业务逻辑、Prisma ORM │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ PostgreSQL (Docker) │ │ localhost:5432 │ └─────────────────────────────────────────┘ ``` --- #### Electron架构(单机版) ``` ┌───────────────────────────────────────────────────────────┐ │ Electron 应用 │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 渲染进程 (Chromium) │ │ │ │ ┌───────────────────────────────────────────────┐ │ │ │ │ │ 前端 (React) ⭐ 复用现有代码 │ │ │ │ │ │ file://app/index.html │ │ │ │ │ └───────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ IPC通信 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 主进程 (Node.js) │ │ │ │ ┌───────────────────────────────────────────────┐ │ │ │ │ │ API逻辑、业务逻辑 ⭐ 复用现有代码 │ │ │ │ │ │ Prisma ORM、本地文件系统 │ │ │ │ │ └───────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ SQLite (嵌入式数据库) │ │ │ │ ~/Documents/YizhengxunData/app.db │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 子进程 (Python/R) │ │ │ │ 文档提取、数据分析 │ │ │ └─────────────────────────────────────────────────────┘ │ └───────────────────────────────────────────────────────────┘ ``` --- ### 核心技术方案 #### 1. 前端代码复用(90%+ 复用) **现有前端代码(Web版):** ```typescript // frontend/src/api/client.ts const API_BASE_URL = 'http://localhost:3001'; export const apiClient = { async get(url: string) { return fetch(`${API_BASE_URL}${url}`); }, async post(url: string, data: any) { return fetch(`${API_BASE_URL}${url}`, { method: 'POST', body: JSON.stringify(data) }); } }; ``` **Electron版前端(修改API调用方式):** ```typescript // electron-frontend/src/api/client.ts // 使用Electron的IPC通信,而不是HTTP const { ipcRenderer } = window.require('electron'); export const apiClient = { async get(url: string) { // 通过IPC发送到主进程 return ipcRenderer.invoke('api-request', { method: 'GET', url }); }, async post(url: string, data: any) { return ipcRenderer.invoke('api-request', { method: 'POST', url, data }); } }; ``` **React组件完全不需要改:** ```typescript // 业务组件完全不变 ✅ function UserList() { const [users, setUsers] = useState([]); useEffect(() => { // API调用方式不变,底层自动适配 apiClient.get('/api/users').then(setUsers); }, []); return
{/* ... */}
; } ``` **复用率:90%+ ✅** - ✅ 所有React组件 - ✅ 所有UI库(Ant Design) - ✅ 所有状态管理(Zustand) - ✅ 所有路由(React Router) - ⚠️ 只需修改API调用层(1个文件) --- #### 2. 后端代码复用(80%+ 复用) **现有后端代码(Web版):** ```typescript // backend/src/routes/users.ts import { FastifyInstance } from 'fastify'; export async function userRoutes(fastify: FastifyInstance) { // GET /api/users fastify.get('/api/users', async (request, reply) => { const users = await prisma.user.findMany(); return users; }); // POST /api/users fastify.post('/api/users', async (request, reply) => { const user = await prisma.user.create({ data: request.body }); return user; }); } ``` **Electron版后端(主进程):** ```typescript // electron-backend/src/ipc-handlers.ts import { ipcMain } from 'electron'; import { prisma } from './prisma-client'; // 复用业务逻辑 ⭐ import { UserService } from '../../../backend/src/services/user.service'; export function setupIpcHandlers() { const userService = new UserService(prisma); // 将HTTP路由转换为IPC Handler ipcMain.handle('api-request', async (event, { method, url, data }) => { // 路由分发(简化版的Fastify) if (url === '/api/users' && method === 'GET') { return userService.findAll(); } if (url === '/api/users' && method === 'POST') { return userService.create(data); } // ... 其他路由 }); } ``` **核心业务逻辑完全复用:** ```typescript // backend/src/services/user.service.ts // 这个文件在Web版和Electron版完全共享 ✅ export class UserService { constructor(private prisma: PrismaClient) {} async findAll() { return this.prisma.user.findMany(); } async create(data: CreateUserDto) { // 业务逻辑 const hashedPassword = await bcrypt.hash(data.password, 10); return this.prisma.user.create({ data: { ...data, password: hashedPassword } }); } } ``` **复用率:80%+ ✅** - ✅ 所有Service层(业务逻辑) - ✅ 所有Prisma Model和查询 - ✅ 所有工具函数 - ⚠️ 需要适配:HTTP路由 → IPC Handler - ⚠️ 需要替换:PostgreSQL → SQLite --- #### 3. 数据库适配(PostgreSQL → SQLite) **Prisma Schema修改(最小改动):** ```prisma // electron-backend/prisma/schema.prisma datasource db { // Web版:PostgreSQL // provider = "postgresql" // url = env("DATABASE_URL") // Electron版:SQLite ⭐ provider = "sqlite" url = "file:./app.db" } // Model定义完全不变 ✅ model User { id String @id @default(uuid()) email String @unique password String name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("users") } ``` **注意事项:** ```typescript // SQLite不支持某些PostgreSQL特性 // ❌ PostgreSQL支持,SQLite不支持: - JSON列类型(需要用TEXT存储) - 复杂的全文搜索 - 某些聚合函数 // ✅ 解决方案: // 1. 用TEXT存储JSON(手动序列化) model Config { id String @id data String // JSON.stringify(data) } // 2. 简化查询(避免复杂SQL) // 3. 在应用层实现某些功能 ``` --- ### 完整项目结构 ``` AIclinicalresearch/ ├── packages/ # 共享代码(Monorepo) │ ├── frontend-core/ # 前端核心(Web + Electron复用) │ ├── backend-core/ # 后端核心(Web + Electron复用) │ └── shared-types/ # 共享类型定义 │ ├── apps/ │ ├── web/ # Web版(当前) │ │ ├── frontend/ │ │ └── backend/ │ │ │ └── electron/ # Electron版 ⭐ │ ├── main/ # 主进程(Node.js后端) │ │ ├── src/ │ │ │ ├── main.ts # Electron入口 │ │ │ ├── ipc-handlers.ts # IPC处理器 │ │ │ ├── services/ # 复用backend-core │ │ │ └── prisma/ │ │ │ └── schema.prisma # SQLite版本 │ │ └── package.json │ │ │ ├── renderer/ # 渲染进程(React前端) │ │ ├── src/ │ │ │ ├── main.tsx │ │ │ ├── api/ # IPC客户端 │ │ │ ├── pages/ # 复用frontend-core │ │ │ └── components/ # 复用frontend-core │ │ └── package.json │ │ │ ├── resources/ # 打包资源 │ │ ├── python/ # Python运行时 │ │ ├── r/ # R运行时(如需要) │ │ └── assets/ │ │ │ └── electron-builder.yml # 打包配置 │ └── ... ``` --- ### 关键实现细节 #### 1. Electron主进程(main.ts) ```typescript // apps/electron/main/src/main.ts import { app, BrowserWindow, ipcMain } from 'electron'; import path from 'path'; import { setupIpcHandlers } from './ipc-handlers'; import { initDatabase } from './database'; import { startPythonService } from './python-service'; let mainWindow: BrowserWindow | null = null; async function createWindow() { // 1. 创建浏览器窗口 mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js') // 安全的IPC桥接 } }); // 2. 加载前端(生产环境) if (app.isPackaged) { mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); } else { // 开发环境:连接到Vite开发服务器 mainWindow.loadURL('http://localhost:5173'); } // 3. 初始化数据库 await initDatabase(); // 4. 启动Python文档处理服务 await startPythonService(); // 5. 设置IPC处理器 setupIpcHandlers(); } // Electron生命周期 app.whenReady().then(createWindow); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); ``` --- #### 2. IPC处理器(ipc-handlers.ts) ```typescript // apps/electron/main/src/ipc-handlers.ts import { ipcMain } from 'electron'; import { prisma } from './prisma-client'; // 复用业务逻辑 ⭐ import { UserService } from '@yizhengxun/backend-core/services'; import { ProjectService } from '@yizhengxun/backend-core/services'; import { ConversationService } from '@yizhengxun/backend-core/services'; export function setupIpcHandlers() { // 初始化服务 const userService = new UserService(prisma); const projectService = new ProjectService(prisma); const conversationService = new ConversationService(prisma); // 用户相关API ipcMain.handle('api:users:list', async () => { return userService.findAll(); }); ipcMain.handle('api:users:create', async (event, data) => { return userService.create(data); }); // 项目相关API ipcMain.handle('api:projects:list', async (event, userId) => { return projectService.findByUser(userId); }); ipcMain.handle('api:projects:create', async (event, data) => { return projectService.create(data); }); // 对话相关API ipcMain.handle('api:conversations:create', async (event, data) => { return conversationService.create(data); }); ipcMain.handle('api:conversations:chat', async (event, data) => { // 调用LLM(本地或云端API) return conversationService.chat(data); }); // 文件操作 ipcMain.handle('api:files:upload', async (event, file) => { // 保存到本地文件系统 const filePath = await saveFile(file); return { url: filePath }; }); } ``` --- #### 3. 前端IPC客户端(api-client.ts) ```typescript // apps/electron/renderer/src/api/client.ts const { ipcRenderer } = window.require('electron'); export const apiClient = { // 用户API users: { async list() { return ipcRenderer.invoke('api:users:list'); }, async create(data: any) { return ipcRenderer.invoke('api:users:create', data); } }, // 项目API projects: { async list(userId: string) { return ipcRenderer.invoke('api:projects:list', userId); }, async create(data: any) { return ipcRenderer.invoke('api:projects:create', data); } }, // 对话API conversations: { async create(data: any) { return ipcRenderer.invoke('api:conversations:create', data); }, async chat(data: any) { return ipcRenderer.invoke('api:conversations:chat', data); } }, // 文件上传 files: { async upload(file: File) { // 将File转换为Buffer const buffer = await file.arrayBuffer(); return ipcRenderer.invoke('api:files:upload', { name: file.name, data: Buffer.from(buffer) }); } } }; ``` --- #### 4. Python子进程管理 ```typescript // apps/electron/main/src/python-service.ts import { spawn, ChildProcess } from 'child_process'; import path from 'path'; import { app } from 'electron'; let pythonProcess: ChildProcess | null = null; export async function startPythonService(): Promise { return new Promise((resolve, reject) => { // Python运行时路径(打包时包含) const pythonPath = app.isPackaged ? path.join(process.resourcesPath, 'python', 'python.exe') : 'python'; // 开发环境使用系统Python // Python脚本路径 const scriptPath = app.isPackaged ? path.join(process.resourcesPath, 'python', 'extraction_service', 'main.py') : path.join(__dirname, '../../../../extraction_service/main.py'); // 启动Python进程 pythonProcess = spawn(pythonPath, [ '-m', 'uvicorn', 'main:app', '--host', '127.0.0.1', '--port', '8000' ], { cwd: path.dirname(scriptPath), stdio: 'pipe' }); // 监听输出 pythonProcess.stdout?.on('data', (data) => { console.log(`Python: ${data}`); if (data.toString().includes('Application startup complete')) { resolve(); } }); pythonProcess.stderr?.on('data', (data) => { console.error(`Python Error: ${data}`); }); pythonProcess.on('error', reject); }); } export function stopPythonService(): void { if (pythonProcess) { pythonProcess.kill(); pythonProcess = null; } } // 应用退出时关闭Python服务 app.on('will-quit', stopPythonService); ``` --- #### 5. 打包配置(electron-builder.yml) ```yaml # apps/electron/electron-builder.yml appId: com.yizhengxun.aiclinical productName: 壹证循AI科研助手 # 打包目录 directories: output: dist buildResources: resources # Windows配置 win: target: - nsis - portable icon: resources/icon.ico # NSIS安装程序配置 nsis: oneClick: false allowToChangeInstallationDirectory: true createDesktopShortcut: always createStartMenuShortcut: true # Mac配置 mac: target: - dmg - zip icon: resources/icon.icns category: public.app-category.productivity # 打包包含的文件 files: - "main/**/*" - "renderer/**/*" - "!**/*.ts" - "!**/*.map" # 额外资源(Python运行时等) extraResources: - from: resources/python to: python - from: ../../../../extraction_service to: python/extraction_service # 打包后大小 asar: true asarUnpack: - "**/*.node" - "resources/**/*" ``` --- ### 单机版的关键技术挑战 #### 挑战1:打包体积(500MB+) **包含内容:** ``` 安装包组成: - Electron框架:~100MB - Node.js运行时:包含在Electron中 - 前端代码(编译后):~10MB - 后端代码(编译后):~5MB - SQLite数据库:~5MB(空库) - Python运行时:~150MB - Python依赖:~100MB - R运行时(可选):~200MB - R依赖(可选):~100MB 总计:500-700MB ``` **优化方案:** 1. ✅ 使用ASAR压缩(Electron默认) 2. ✅ 不包含R运行时(仅云端版需要) 3. ✅ Python依赖精简(只包含必需的包) 4. ✅ 增量更新(只下载变化的部分) --- #### 挑战2:跨平台兼容性 **需要分别打包:** - Windows x64 - Windows ARM64(Surface等设备) - macOS x64 - macOS ARM64(Apple Silicon) - Linux x64 **测试工作量:** - 每个平台都需要独立测试 - 安装、升级、卸载流程 - 权限问题、文件路径问题 --- #### 挑战3:自动更新 **electron-updater配置:** ```typescript // apps/electron/main/src/updater.ts import { autoUpdater } from 'electron-updater'; import { dialog } from 'electron'; export function setupAutoUpdater() { // 配置更新服务器 autoUpdater.setFeedURL({ provider: 'generic', url: 'https://releases.yizhengxun.com' }); // 检查更新 autoUpdater.checkForUpdatesAndNotify(); // 发现新版本 autoUpdater.on('update-available', () => { dialog.showMessageBox({ type: 'info', title: '发现新版本', message: '检测到新版本,是否立即下载?', buttons: ['是', '否'] }).then((result) => { if (result.response === 0) { autoUpdater.downloadUpdate(); } }); }); // 下载完成 autoUpdater.on('update-downloaded', () => { dialog.showMessageBox({ type: 'info', title: '更新已就绪', message: '新版本已下载,是否立即安装?', buttons: ['立即安装', '稍后'] }).then((result) => { if (result.response === 0) { autoUpdater.quitAndInstall(); } }); }); } ``` --- ## 📊 总结对比 ### 三种部署方式对比 | 维度 | 云端SaaS | 独立产品包 | Electron单机版 | |------|---------|----------|---------------| | **部署方式** | 云端服务器 | Docker容器 | 本地安装 | | **数据存储** | 云端PostgreSQL | 本地PostgreSQL | 本地SQLite | | **网络需求** | 必须联网 | 可离线(LLM除外) | 完全离线 | | **更新方式** | 无感知更新 | Docker镜像更新 | 应用内更新 | | **安装难度** | 无需安装 | Docker部署 | 一键安装 | | **代码复用** | 100% | 80%(精简版) | 90% | | **适用客户** | 个人、小机构 | 医院、大机构 | 个人医生 | | **商业模式** | 订阅制 | 一次性License | 一次性购买 | | **维护成本** | 低 | 中 | 高 | --- ## 🎯 最终建议 ### 分阶段实施 **阶段一(当前-6个月):** - ✅ 专注云端SaaS版 - ✅ 完善7个业务模块 - ✅ 建立Monorepo架构(为未来打基础) **阶段二(6-12个月):** - ✅ 开发独立产品包(审稿系统、AI文献) - ✅ 支持Docker本地化部署 - ✅ 验证商业模式 **阶段三(12-18个月):** - ✅ 开发Electron单机版(如有需求) - ✅ 支持完全离线使用 - ✅ 扩展个人用户市场 --- **您想先深入哪个方案?** 1. 模块独立部署的详细实施? 2. Electron单机版的开发计划? 3. Monorepo架构的设计?