Files
AIclinicalresearch/docs/00-系统总体设计/06-模块独立部署与单机版方案.md
HaHafeng 66255368b7 feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1):
- Database: Add user_modules table for fine-grained module permissions
- Database: Add 4 user permissions (view/create/edit/delete) to role_permissions
- Backend: UserService (780 lines) - CRUD with tenant isolation
- Backend: UserController + UserRoutes (648 lines) - 13 API endpoints
- Backend: Batch import users from Excel
- Frontend: UserListPage (412 lines) - list/filter/search/pagination
- Frontend: UserFormPage (341 lines) - create/edit with module config
- Frontend: UserDetailPage (393 lines) - details/tenant/module management
- Frontend: 3 modal components (592 lines) - import/assign/configure
- API: GET/POST/PUT/DELETE /api/admin/users/* endpoints

Architecture Upgrade - Module Permission System:
- Backend: Add getUserModules() method in auth.service
- Backend: Login API returns modules array in user object
- Frontend: AuthContext adds hasModule() method
- Frontend: Navigation filters modules based on user.modules
- Frontend: RouteGuard checks requiredModule instead of requiredVersion
- Frontend: Remove deprecated version-based permission system
- UX: Only show accessible modules in navigation (clean UI)
- UX: Smart redirect after login (avoid 403 for regular users)

Fixes:
- Fix UTF-8 encoding corruption in ~100 docs files
- Fix pageSize type conversion in userService (String to Number)
- Fix authUser undefined error in TopNavigation
- Fix login redirect logic with role-based access check
- Update Git commit guidelines v1.2 with UTF-8 safety rules

Database Changes:
- CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled)
- ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code)
- INSERT 4 permissions + role assignments
- UPDATE PUBLIC tenant with 8 module subscriptions

Technical:
- Backend: 5 new files (~2400 lines)
- Frontend: 10 new files (~2500 lines)
- Docs: 1 development record + 2 status updates + 1 guideline update
- Total: ~4900 lines of code

Status: User management 100% complete, module permission system operational
2026-01-16 13:42:10 +08:00

1567 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模块独立部署与单机版方案
> **文档版本:** v1.0
> **创建日期:** 2025-11-06
> **最后更新:** 2025-11-06
> **文档状态:** 架构设计
> **作者:** 技术架构师
---
## 📋 核心问题
1. **如何实现每个模块独立部署?**
- 模块依赖平台层和通用能力层,如何独立?
- 如何变成独立产品销售?
- 如何实现本地化部署?
2. **如何开发单机版?**
- 如何利用Electron框架
- 如何复用现有代码?
- 架构如何设计?
---
## 🎯 Part 1模块独立部署方案
### 核心理念:独立部署 ≠ 完全孤立
**关键洞察:**
```
独立部署的真正含义:
✅ 可以单独打包
✅ 可以单独运行
✅ 可以单独销售
✅ 可以单独升级
但不是:
❌ 完全不依赖其他代码
❌ 完全不共享基础设施
```
**类比:**
```
就像一个独立的手机App
- 它依赖操作系统Android/iOS
- 它依赖系统API摄像头、定位等
- 但它可以独立下载、安装、运行、卸载
模块独立部署也是一样:
- 它依赖平台层(用户认证、存储等)
- 它依赖通用能力层LLM网关、文档处理等
- 但它可以独立打包、部署、销售
```
---
## 📦 方案一:完整打包(推荐用于独立产品)
### 适用场景
- ✅ 模块作为独立产品销售(如审稿系统)
- ✅ 客户要求完全本地化部署
- ✅ 不依赖其他模块或平台
### 核心思路
**将依赖的平台层和通用能力层一起打包**
```
审稿系统独立产品包:
┌─────────────────────────────────────────┐
│ RVW审稿系统独立产品 │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 业务模块层 │ │
│ │ RVW稿件审查 │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 通用能力层(必需部分) │ │
│ │ - LLM网关 │ │
│ │ - 文档处理引擎 │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 平台基础层(必需部分) │ │
│ │ - 用户认证(简化版) │ │
│ │ - 存储服务 │ │
│ │ - 监控日志 │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 独立数据库 │ │
│ │ PostgreSQLDocker │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
---
### 技术实现
#### 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-2DeepSeek + 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 2Electron单机版方案
### 为什么需要单机版?
**核心需求:**
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 <div>{/* ... */}</div>;
}
```
**复用率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<void> {
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 ARM64Surface等设备
- macOS x64
- macOS ARM64Apple 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架构的设计