feat(ssa): Complete T-test end-to-end testing with 9 bug fixes - Phase 1 core 85% complete. R service: missing value auto-filter. Backend: error handling, variable matching, dynamic filename. Frontend: module activation, session isolation, error propagation. Full flow verified.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
580
docs/02-通用能力层/06-R统计引擎/01-R统计引擎架构与部署指南.md
Normal file
580
docs/02-通用能力层/06-R统计引擎/01-R统计引擎架构与部署指南.md
Normal file
@@ -0,0 +1,580 @@
|
||||
# R 统计引擎架构与部署指南
|
||||
|
||||
> **版本:** v1.0
|
||||
> **创建日期:** 2026-02-19
|
||||
> **维护者:** SSA-Pro 开发团队
|
||||
> **状态:** ✅ 生产就绪
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [概述](#1-概述)
|
||||
2. [架构设计](#2-架构设计)
|
||||
3. [Docker 镜像构建](#3-docker-镜像构建)
|
||||
4. [部署指南](#4-部署指南)
|
||||
5. [API 参考](#5-api-参考)
|
||||
6. [开发指南](#6-开发指南)
|
||||
7. [运维指南](#7-运维指南)
|
||||
8. [常见问题](#8-常见问题)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 什么是 R 统计引擎
|
||||
|
||||
R 统计引擎是平台的**专用统计计算服务**,基于 Docker 容器化部署,提供:
|
||||
|
||||
- 🧮 **严谨的统计分析能力**(T 检验、方差分析、回归等)
|
||||
- 🛡️ **统计护栏**(正态性检验、方差齐性检验等)
|
||||
- 📊 **可视化输出**(Base64 编码的图表)
|
||||
- 📝 **可复现代码生成**(APA 格式的 R 脚本)
|
||||
|
||||
### 1.2 定位
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 业务模块层 │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ SSA-Pro │ │ 其他 │ │ 其他 │ │
|
||||
│ │ 智能统计 │ │ 模块 │ │ 模块 │ │
|
||||
│ └────┬────┘ └─────────┘ └─────────┘ │
|
||||
├───────┼─────────────────────────────────────────────────────┤
|
||||
│ ▼ 通用能力层 │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ R 统计引擎 (Docker) │ │
|
||||
│ │ • /health 健康检查 │ │
|
||||
│ │ • /api/v1/tools 工具列表 │ │
|
||||
│ │ • /api/v1/skills 技能执行 │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.3 技术栈
|
||||
|
||||
| 组件 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| R | 4.3.3 | 统计计算核心 |
|
||||
| plumber | 1.2.1 | REST API 框架 |
|
||||
| ggplot2 | 最新 | 数据可视化 |
|
||||
| car | 3.1-2 | 高级统计检验 |
|
||||
| dplyr/tidyr | 最新 | 数据处理 |
|
||||
| Docker | 24+ | 容器化部署 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 Brain-Hand 模型
|
||||
|
||||
R 统计引擎采用 **Brain-Hand 分离架构**:
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ Node.js │ │ R Docker │
|
||||
│ (Brain) │ │ (Hand) │
|
||||
├──────────────────┤ ├──────────────────┤
|
||||
│ • 业务逻辑 │ HTTP │ • 统计计算 │
|
||||
│ • 认证鉴权 │ ───────> │ • 数据处理 │
|
||||
│ • OSS 签名 │ │ • 图表生成 │
|
||||
│ • 结果解释 │ <─────── │ • 代码生成 │
|
||||
└──────────────────┘ JSON └──────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 数据传输协议
|
||||
|
||||
支持两种数据传输方式:
|
||||
|
||||
| 方式 | 条件 | 字段 |
|
||||
|------|------|------|
|
||||
| **inline** | 数据 < 2MB | `data_source.data` (JSON) |
|
||||
| **oss** | 数据 >= 2MB | `data_source.oss_url` (预签名 URL) |
|
||||
|
||||
```json
|
||||
// 方式 1: inline
|
||||
{
|
||||
"data_source": {
|
||||
"type": "inline",
|
||||
"data": [{"group": "A", "value": 10}, ...]
|
||||
}
|
||||
}
|
||||
|
||||
// 方式 2: oss (预签名 URL)
|
||||
{
|
||||
"data_source": {
|
||||
"type": "oss",
|
||||
"oss_url": "https://bucket.oss.com/data.csv?signature=xxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 安全设计
|
||||
|
||||
| 安全措施 | 实现方式 |
|
||||
|----------|----------|
|
||||
| 非特权用户 | `USER appuser` |
|
||||
| 路径遍历防护 | `tool_code` 正则白名单 `^[A-Z][A-Z0-9_]*$` |
|
||||
| OSS 密钥隔离 | Node.js 生成预签名 URL,R 无需持有密钥 |
|
||||
| 健康检查 | Docker HEALTHCHECK |
|
||||
|
||||
---
|
||||
|
||||
## 3. Docker 镜像构建
|
||||
|
||||
### 3.1 完整 Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM rocker/r-ver:4.3
|
||||
|
||||
LABEL maintainer="dev-team@aiclinicalresearch.com"
|
||||
LABEL version="1.0.1"
|
||||
LABEL description="SSA-Pro R Statistics Service"
|
||||
|
||||
# 安装系统依赖(包括 R 包编译所需的库)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libcurl4-openssl-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
libsodium-dev \
|
||||
zlib1g-dev \
|
||||
libnlopt-dev \
|
||||
liblapack-dev \
|
||||
libblas-dev \
|
||||
gfortran \
|
||||
pkg-config \
|
||||
cmake \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 直接安装 R 包
|
||||
RUN R -e "install.packages(c( \
|
||||
'plumber', \
|
||||
'jsonlite', \
|
||||
'ggplot2', \
|
||||
'glue', \
|
||||
'dplyr', \
|
||||
'tidyr', \
|
||||
'base64enc', \
|
||||
'yaml', \
|
||||
'car', \
|
||||
'httr' \
|
||||
), repos='https://cloud.r-project.org/', Ncpus=2)"
|
||||
|
||||
# 安全加固:创建非特权用户
|
||||
RUN useradd -m -s /bin/bash appuser
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 复制应用代码
|
||||
COPY plumber.R plumber.R
|
||||
COPY utils/ utils/
|
||||
COPY tools/ tools/
|
||||
COPY tests/ tests/
|
||||
|
||||
# 设置目录权限
|
||||
RUN chown -R appuser:appuser /app
|
||||
|
||||
# 切换到非特权用户
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# 环境变量
|
||||
ENV DEV_MODE="false"
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/health || exit 1
|
||||
|
||||
# 启动服务
|
||||
CMD ["R", "-e", "plumber::plumb('plumber.R')$run(host='0.0.0.0', port=8080)"]
|
||||
```
|
||||
|
||||
### 3.2 系统依赖说明
|
||||
|
||||
| 依赖包 | 用途 |
|
||||
|--------|------|
|
||||
| `libcurl4-openssl-dev` | httr 包(HTTP 请求) |
|
||||
| `libssl-dev` | openssl 包(加密) |
|
||||
| `libxml2-dev` | xml2 包(XML 解析) |
|
||||
| `libsodium-dev` | sodium 包(加密) |
|
||||
| `zlib1g-dev` | httpuv 包(Web 服务器) |
|
||||
| `libnlopt-dev` | nloptr 包(优化算法) |
|
||||
| `liblapack-dev` | 线性代数计算 |
|
||||
| `libblas-dev` | 基础线性代数 |
|
||||
| `gfortran` | Fortran 编译器(部分 R 包需要) |
|
||||
| `cmake` | nloptr 包构建 |
|
||||
| `curl` | 健康检查 |
|
||||
|
||||
### 3.3 构建命令
|
||||
|
||||
```bash
|
||||
# 本地构建
|
||||
cd r-statistics-service
|
||||
docker build -t ssa-r-statistics:1.0.1 .
|
||||
|
||||
# 查看镜像
|
||||
docker images ssa-r-statistics
|
||||
|
||||
# 预期输出
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
ssa-r-statistics 1.0.1 xxxxxxxxxxxx x minutes ago 1.81GB
|
||||
```
|
||||
|
||||
### 3.4 构建时间参考
|
||||
|
||||
| 阶段 | 耗时 |
|
||||
|------|------|
|
||||
| 基础镜像下载 | ~2 分钟(首次) |
|
||||
| 系统依赖安装 | ~1 分钟 |
|
||||
| R 包安装 | ~6 分钟 |
|
||||
| **总计** | **~9 分钟** |
|
||||
|
||||
---
|
||||
|
||||
## 4. 部署指南
|
||||
|
||||
### 4.1 开发环境
|
||||
|
||||
使用 docker-compose:
|
||||
|
||||
```yaml
|
||||
# r-statistics-service/docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
ssa-r-service:
|
||||
build: .
|
||||
container_name: ssa-r-statistics
|
||||
ports:
|
||||
- "8082:8080" # 主机8082 → 容器8080(REDCap占用8080/8081)
|
||||
environment:
|
||||
- DEV_MODE=true
|
||||
volumes:
|
||||
# 开发环境挂载:支持热重载
|
||||
- ./tools:/app/tools
|
||||
- ./utils:/app/utils
|
||||
- ./tests:/app/tests
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"] # 容器内部仍是8080
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
```
|
||||
|
||||
启动命令:
|
||||
```bash
|
||||
cd r-statistics-service
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 4.2 生产环境 (SAE)
|
||||
|
||||
```yaml
|
||||
# SAE 配置
|
||||
容器镜像: registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.1
|
||||
实例规格: 2 vCPU, 4 GB
|
||||
最小实例: 1
|
||||
最大实例: 5
|
||||
端口: 8080
|
||||
|
||||
环境变量:
|
||||
DEV_MODE: "false"
|
||||
```
|
||||
|
||||
### 4.3 环境变量
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `DEV_MODE` | `false` | 开发模式(启用热重载,每次请求重新加载工具脚本) |
|
||||
|
||||
> **说明**:开发环境和生产环境都使用真实 OSS,无需 Mock 数据。
|
||||
> - 开发环境:`ai-clinical-data-dev` bucket
|
||||
> - 生产环境:`ai-clinical-data` bucket
|
||||
|
||||
### 4.4 端口配置
|
||||
|
||||
| 环境 | 主机端口 | 容器端口 | 说明 |
|
||||
|------|----------|----------|------|
|
||||
| **开发环境** | 8082 | 8080 | 避免与 REDCap 8080/8081 冲突 |
|
||||
| **生产环境 (SAE)** | 8080 | 8080 | 云端无端口冲突 |
|
||||
|
||||
> **注意**:Node.js 后端通过 `R_SERVICE_URL` 环境变量配置 R 服务地址,默认值为 `http://localhost:8082`。
|
||||
|
||||
---
|
||||
|
||||
## 5. API 参考
|
||||
|
||||
### 5.1 健康检查
|
||||
|
||||
```http
|
||||
GET /health
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2026-02-19 08:00:00",
|
||||
"version": "1.0.1",
|
||||
"dev_mode": true,
|
||||
"tools_loaded": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 工具列表
|
||||
|
||||
```http
|
||||
GET /api/v1/tools
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"tools": ["t_test_ind", "anova_oneway"],
|
||||
"count": 2
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 执行技能
|
||||
|
||||
```http
|
||||
POST /api/v1/skills/{tool_code}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
```json
|
||||
{
|
||||
"data_source": {
|
||||
"type": "inline",
|
||||
"data": [...]
|
||||
},
|
||||
"params": {
|
||||
"group_var": "group",
|
||||
"value_var": "value"
|
||||
},
|
||||
"guardrails": {
|
||||
"check_normality": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应:**
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "分析完成",
|
||||
"warnings": null,
|
||||
"results": {
|
||||
"method": "Welch Two Sample t-test",
|
||||
"statistic": -5.196,
|
||||
"df": 5.98,
|
||||
"p_value": 0.002,
|
||||
"p_value_fmt": "p = .002"
|
||||
},
|
||||
"plots": ["data:image/png;base64,..."],
|
||||
"trace_log": [...],
|
||||
"reproducible_code": "..."
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"error_code": "E001",
|
||||
"error_type": "business",
|
||||
"message": "列名 'xxx' 在数据中不存在",
|
||||
"user_hint": "请检查变量名是否拼写正确"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 开发指南
|
||||
|
||||
### 6.1 添加新工具
|
||||
|
||||
1. 在 `tools/` 目录创建 R 脚本:
|
||||
|
||||
```r
|
||||
# tools/my_analysis.R
|
||||
|
||||
#' @tool_code ST_MY_ANALYSIS
|
||||
#' @name 我的分析工具
|
||||
#' @version 1.0.0
|
||||
|
||||
# 统一入口函数
|
||||
run_analysis <- function(input) {
|
||||
# 加载数据
|
||||
df <- load_input_data(input)
|
||||
|
||||
# 参数
|
||||
p <- input$params
|
||||
|
||||
# 护栏检查
|
||||
# ...
|
||||
|
||||
# 核心计算
|
||||
# ...
|
||||
|
||||
# 返回结果
|
||||
return(list(
|
||||
status = "success",
|
||||
message = "分析完成",
|
||||
results = list(...)
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
2. 重启服务(开发模式无需重启)
|
||||
|
||||
3. 测试:
|
||||
```bash
|
||||
curl -X POST http://localhost:8082/api/v1/skills/ST_MY_ANALYSIS \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data_source": {...}, "params": {...}}'
|
||||
```
|
||||
|
||||
### 6.2 工具命名规范
|
||||
|
||||
| 项目 | 规范 |
|
||||
|------|------|
|
||||
| 文件名 | 小写下划线:`t_test_ind.R` |
|
||||
| tool_code | 大写下划线:`ST_T_TEST_IND` |
|
||||
| 入口函数 | 固定名称:`run_analysis` |
|
||||
|
||||
### 6.3 结果格式规范
|
||||
|
||||
```r
|
||||
return(list(
|
||||
status = "success" | "error" | "blocked",
|
||||
message = "...",
|
||||
warnings = c("...") | NULL,
|
||||
results = list(
|
||||
# 统计结果
|
||||
),
|
||||
plots = list(
|
||||
"data:image/png;base64,..."
|
||||
),
|
||||
trace_log = c("..."),
|
||||
reproducible_code = "..."
|
||||
))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 运维指南
|
||||
|
||||
### 7.1 日志查看
|
||||
|
||||
```bash
|
||||
# 实时日志
|
||||
docker logs -f ssa-r-statistics
|
||||
|
||||
# 最近 100 行
|
||||
docker logs --tail 100 ssa-r-statistics
|
||||
```
|
||||
|
||||
### 7.2 性能监控
|
||||
|
||||
```bash
|
||||
# 容器资源使用
|
||||
docker stats ssa-r-statistics
|
||||
```
|
||||
|
||||
### 7.3 重启服务
|
||||
|
||||
```bash
|
||||
# 开发环境
|
||||
docker-compose restart
|
||||
|
||||
# 生产环境 (SAE)
|
||||
通过 SAE 控制台重启实例
|
||||
```
|
||||
|
||||
### 7.4 镜像更新
|
||||
|
||||
```bash
|
||||
# 1. 构建新镜像
|
||||
docker build -t ssa-r-statistics:1.0.2 .
|
||||
|
||||
# 2. 推送到镜像仓库
|
||||
docker tag ssa-r-statistics:1.0.2 registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.2
|
||||
docker push registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.2
|
||||
|
||||
# 3. 更新 SAE 部署
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见问题
|
||||
|
||||
### Q1: 构建时 httpuv 安装失败
|
||||
|
||||
**错误:** `fatal error: zlib.h: No such file or directory`
|
||||
|
||||
**解决:** 添加 `zlib1g-dev` 到系统依赖
|
||||
|
||||
### Q2: 构建时 nloptr 安装失败
|
||||
|
||||
**错误:** `CMAKE NOT FOUND`
|
||||
|
||||
**解决:** 添加 `cmake` 到系统依赖
|
||||
|
||||
### Q3: /tmp 权限问题
|
||||
|
||||
**错误:** `cannot open file '/tmp/Rtmpxxx': No such file or directory`
|
||||
|
||||
**解决:** 不要在启动命令中清理 /tmp
|
||||
|
||||
### Q4: DEV_MODE 热重载不生效
|
||||
|
||||
**原因:** 没有挂载 volumes
|
||||
|
||||
**解决:**
|
||||
```yaml
|
||||
volumes:
|
||||
- ./tools:/app/tools
|
||||
```
|
||||
|
||||
### Q5: 容器启动后无法访问
|
||||
|
||||
**检查:**
|
||||
1. 端口映射是否正确
|
||||
2. 健康检查是否通过
|
||||
3. 查看容器日志
|
||||
|
||||
---
|
||||
|
||||
## 附录:文件结构
|
||||
|
||||
```
|
||||
r-statistics-service/
|
||||
├── Dockerfile # 生产镜像定义
|
||||
├── docker-compose.yml # 开发环境编排
|
||||
├── renv.lock # R 包版本锁定(备用)
|
||||
├── .Rprofile # R 启动配置(备用)
|
||||
├── plumber.R # API 入口
|
||||
├── utils/
|
||||
│ ├── data_loader.R # 数据加载(预签名 URL)
|
||||
│ ├── guardrails.R # 统计护栏
|
||||
│ ├── error_codes.R # 错误映射
|
||||
│ └── result_formatter.R # 结果格式化
|
||||
├── tools/
|
||||
│ └── t_test_ind.R # 独立样本 T 检验
|
||||
├── tests/
|
||||
│ └── fixtures/
|
||||
│ └── normal_data.csv # 测试数据
|
||||
├── metadata/ # 工具元数据(预留)
|
||||
└── templates/ # 解释模板(预留)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
Reference in New Issue
Block a user