Compare commits

...

8 Commits

Author SHA1 Message Date
yuliang_guo
7555de2275 fix: 修复课程库加载 - 后端限制每页最多100条
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 15:12:34 +08:00
yuliang_guo
344d8c1770 feat: 完善成长路径管理功能
All checks were successful
continuous-integration/drone/push Build is passing
新增功能:
1. 阶段自定义管理 - 添加/删除/编辑阶段名称
2. 列表分页功能
3. 状态筛选(启用/禁用)
4. 课程分类筛选
5. 岗位全选按钮
6. 创建时间列显示
7. 点击必修/选修标签直接切换

画布高度根据阶段数量动态调整

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 15:05:57 +08:00
yuliang_guo
8892511f10 fix: 后端保存和返回节点位置坐标(position_x, position_y)
Some checks failed
continuous-integration/drone/push Build is failing
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 15:00:54 +08:00
yuliang_guo
973ce53bf3 feat: 完善成长路径画布设计器
All checks were successful
continuous-integration/drone/push Build is passing
后端:
- 添加 position_x, position_y 字段保存节点位置

前端:
- 支持从节点右侧圆点拖拽出箭头连接到其他课程
- 自动根据节点Y坐标识别所属阶段
- 保存并恢复节点位置,不再重置
- 阶段区域高亮显示
- 循环依赖检测

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 14:55:01 +08:00
yuliang_guo
9c916195c6 feat: 恢复成长路径画布式设计器
All checks were successful
continuous-integration/drone/push Build is passing
- 右侧改为画布式设计器,节点可自由拖拽定位
- 支持箭头连接线显示前置课程依赖关系
- 阶段分隔线可视化显示
- 设置前置课程弹窗,用箭头连接
- 自动布局和清空画布功能
- 保留列表管理、多岗位关联等功能

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 14:46:42 +08:00
yuliang_guo
20905b72cc feat: 成长路径管理添加拖拽排序功能
All checks were successful
continuous-integration/drone/push Build is passing
- 已选课程支持拖拽调整顺序
- 支持跨阶段拖拽移动课程
- 拖拽时显示视觉反馈(高亮线条)
- 拖拽到空阶段时显示占位提示
- 自动更新课程排序编号

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 14:34:37 +08:00
yuliang_guo
e110067840 feat: 添加统一启动脚本,支持通过环境变量配置workers数量
All checks were successful
continuous-integration/drone/push Build is passing
- 新增 start.sh 启动脚本,根据 WORKERS/RELOAD 环境变量自动配置
- 修改 Dockerfile 使用启动脚本,默认 WORKERS=4
- 更新 docker-compose.prod-multi.yml,所有租户使用环境变量配置
- 生产环境默认4个workers,提升并发处理能力

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 14:25:34 +08:00
yuliang_guo
879247c8e9 docs: 添加MinIO对象存储配置文档
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 14:11:09 +08:00
9 changed files with 1307 additions and 1012 deletions

View File

@@ -43,9 +43,18 @@ RUN pip install --upgrade pip && \
# 复制应用代码 # 复制应用代码
COPY app/ ./app/ COPY app/ ./app/
# 复制启动脚本
COPY start.sh ./start.sh
RUN chmod +x ./start.sh
# 创建上传目录和日志目录 # 创建上传目录和日志目录
RUN mkdir -p uploads logs RUN mkdir -p uploads logs
# 默认环境变量可通过docker-compose或环境变量覆盖
ENV WORKERS=4 \
RELOAD=false \
TIMEOUT_KEEP_ALIVE=600
# 暴露端口 # 暴露端口
EXPOSE 8000 EXPOSE 8000
@@ -53,5 +62,5 @@ EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1 CMD curl -f http://localhost:8000/health || exit 1
# 启动命令(生产模式,无热重载) # 使用启动脚本
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4", "--timeout-keep-alive", "600"] CMD ["./start.sh"]

View File

@@ -86,6 +86,14 @@ class GrowthPathNode(BaseModel, SoftDeleteMixin):
estimated_days: Mapped[int] = mapped_column( estimated_days: Mapped[int] = mapped_column(
Integer, default=7, nullable=False, comment="预计学习天数" Integer, default=7, nullable=False, comment="预计学习天数"
) )
# 画布位置(用于可视化编辑器)
position_x: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, default=0, comment="画布X坐标"
)
position_y: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, default=0, comment="画布Y坐标"
)
# 关联关系 # 关联关系
growth_path: Mapped["GrowthPath"] = relationship( # noqa: F821 growth_path: Mapped["GrowthPath"] = relationship( # noqa: F821

View File

@@ -28,6 +28,8 @@ class NodeBase(BaseModel):
is_required: bool = Field(True, description="是否必修") is_required: bool = Field(True, description="是否必修")
prerequisites: Optional[List[int]] = Field(None, description="前置节点IDs") prerequisites: Optional[List[int]] = Field(None, description="前置节点IDs")
estimated_days: int = Field(7, description="预计学习天数") estimated_days: int = Field(7, description="预计学习天数")
position_x: Optional[int] = Field(0, description="画布X坐标")
position_y: Optional[int] = Field(0, description="画布Y坐标")
# ===================================================== # =====================================================

View File

@@ -95,6 +95,8 @@ class GrowthPathService:
is_required=node_data.is_required, is_required=node_data.is_required,
prerequisites=node_data.prerequisites, prerequisites=node_data.prerequisites,
estimated_days=node_data.estimated_days, estimated_days=node_data.estimated_days,
position_x=node_data.position_x,
position_y=node_data.position_y,
) )
db.add(node) db.add(node)
@@ -147,6 +149,8 @@ class GrowthPathService:
is_required=node_data.is_required, is_required=node_data.is_required,
prerequisites=node_data.prerequisites, prerequisites=node_data.prerequisites,
estimated_days=node_data.estimated_days, estimated_days=node_data.estimated_days,
position_x=node_data.position_x,
position_y=node_data.position_y,
) )
db.add(node) db.add(node)
@@ -222,6 +226,8 @@ class GrowthPathService:
"is_required": node.is_required, "is_required": node.is_required,
"prerequisites": node.prerequisites, "prerequisites": node.prerequisites,
"estimated_days": node.estimated_days, "estimated_days": node.estimated_days,
"position_x": node.position_x,
"position_y": node.position_y,
"created_at": node.created_at, "created_at": node.created_at,
"updated_at": node.updated_at, "updated_at": node.updated_at,
}) })

View File

@@ -1,64 +1,38 @@
#!/bin/bash #!/bin/bash
# 统一启动脚本 - 根据环境变量自动配置
# 颜色定义 # 默认配置
RED='\033[0;31m' HOST=${HOST:-0.0.0.0}
GREEN='\033[0;32m' PORT=${PORT:-8000}
YELLOW='\033[1;33m' WORKERS=${WORKERS:-1}
NC='\033[0m' # No Color RELOAD=${RELOAD:-false}
TIMEOUT_KEEP_ALIVE=${TIMEOUT_KEEP_ALIVE:-600}
echo -e "${GREEN}考培练系统后端启动脚本${NC}" echo "=============================================="
echo "================================" echo " KaoPeiLian Backend Starting..."
echo "=============================================="
echo " HOST: $HOST"
echo " PORT: $PORT"
echo " WORKERS: $WORKERS"
echo " RELOAD: $RELOAD"
echo " TIMEOUT_KEEP_ALIVE: $TIMEOUT_KEEP_ALIVE"
echo "=============================================="
# 检查Python版本 # 构建启动命令
echo -e "${YELLOW}检查Python版本...${NC}" CMD="uvicorn app.main:app --host $HOST --port $PORT --timeout-keep-alive $TIMEOUT_KEEP_ALIVE"
python_version=$(python3 --version 2>&1)
if [[ $? -eq 0 ]]; then if [ "$RELOAD" = "true" ]; then
echo -e "${GREEN}$python_version${NC}" # 开发模式启用热重载不支持多workers
CMD="$CMD --reload --reload-dir /app/app"
echo "Mode: Development (hot reload enabled)"
else else
echo -e "${RED}✗ Python3未安装${NC}" # 生产模式多workers
exit 1 CMD="$CMD --workers $WORKERS"
echo "Mode: Production ($WORKERS workers)"
fi fi
# 检查虚拟环境 echo ""
if [ ! -d "venv" ]; then echo "Executing: $CMD"
echo -e "${YELLOW}创建虚拟环境...${NC}" echo ""
python3 -m venv venv
fi
# 激活虚拟环境 exec $CMD
echo -e "${YELLOW}激活虚拟环境...${NC}"
source venv/bin/activate
# 安装依赖
echo -e "${YELLOW}安装依赖...${NC}"
pip install -q -r requirements/base.txt
# 检查.env文件
if [ ! -f ".env" ]; then
echo -e "${YELLOW}创建.env文件...${NC}"
cp .env.example .env
echo -e "${GREEN}✓ 已创建.env文件请根据需要修改配置${NC}"
fi
# 检查数据库连接
echo -e "${YELLOW}检查数据库连接...${NC}"
python -c "
import os
from dotenv import load_dotenv
load_dotenv()
db_url = os.getenv('DATABASE_URL', '')
if 'mysql' in db_url:
print('✓ 数据库配置已设置')
else:
print('⚠ 请检查数据库配置')
" 2>/dev/null
# 启动服务
echo -e "${GREEN}启动开发服务器...${NC}"
echo "================================"
echo -e "API文档: ${GREEN}http://localhost:8000/api/docs${NC}"
echo -e "健康检查: ${GREEN}http://localhost:8000/health${NC}"
echo "================================"
# 启动uvicorn
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

View File

@@ -78,16 +78,17 @@ services:
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- PYTHONPATH=/app - PYTHONPATH=/app
- WORKERS=4 # 生产环境4个workers
- RELOAD=false # 生产环境关闭热重载
ports: ports:
- "8010:8000" - "8010:8000"
volumes: volumes:
- ./kaopeilian-backend/app:/app/app # 代码热重载 - ./kaopeilian-backend/app:/app/app
- /data/prod-envs/uploads-hua:/app/uploads - /data/prod-envs/uploads-hua:/app/uploads
- /data/prod-envs/logs-hua:/app/logs - /data/prod-envs/logs-hua:/app/logs
- /data/prod-envs/secrets:/app/secrets:ro - /data/prod-envs/secrets:/app/secrets:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
networks: networks:
- prod-network - prod-network
- kaopeilian-network - kaopeilian-network
@@ -164,16 +165,17 @@ services:
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- PYTHONPATH=/app - PYTHONPATH=/app
- WORKERS=4
- RELOAD=false
ports: ports:
- "8011:8000" - "8011:8000"
volumes: volumes:
- ./kaopeilian-backend/app:/app/app # 代码热重载 - ./kaopeilian-backend/app:/app/app
- /data/prod-envs/uploads-yy:/app/uploads - /data/prod-envs/uploads-yy:/app/uploads
- /data/prod-envs/logs-yy:/app/logs - /data/prod-envs/logs-yy:/app/logs
- /data/prod-envs/secrets:/app/secrets:ro - /data/prod-envs/secrets:/app/secrets:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
networks: networks:
- prod-network - prod-network
- kaopeilian-network - kaopeilian-network
@@ -250,16 +252,17 @@ services:
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- PYTHONPATH=/app - PYTHONPATH=/app
- WORKERS=4
- RELOAD=false
ports: ports:
- "8012:8000" - "8012:8000"
volumes: volumes:
- ./kaopeilian-backend/app:/app/app # 代码热重载 - ./kaopeilian-backend/app:/app/app
- /data/prod-envs/uploads-hl:/app/uploads - /data/prod-envs/uploads-hl:/app/uploads
- /data/prod-envs/logs-hl:/app/logs - /data/prod-envs/logs-hl:/app/logs
- /data/prod-envs/secrets:/app/secrets:ro - /data/prod-envs/secrets:/app/secrets:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
networks: networks:
- prod-network - prod-network
- kaopeilian-network - kaopeilian-network
@@ -336,16 +339,17 @@ services:
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- PYTHONPATH=/app - PYTHONPATH=/app
- WORKERS=4
- RELOAD=false
ports: ports:
- "8013:8000" - "8013:8000"
volumes: volumes:
- ./kaopeilian-backend/app:/app/app # 代码热重载 - ./kaopeilian-backend/app:/app/app
- /data/prod-envs/uploads-xy:/app/uploads - /data/prod-envs/uploads-xy:/app/uploads
- /data/prod-envs/logs-xy:/app/logs - /data/prod-envs/logs-xy:/app/logs
- /data/prod-envs/secrets:/app/secrets:ro - /data/prod-envs/secrets:/app/secrets:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
networks: networks:
- prod-network - prod-network
- kaopeilian-network - kaopeilian-network
@@ -423,16 +427,17 @@ services:
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- PYTHONPATH=/app - PYTHONPATH=/app
- WORKERS=4
- RELOAD=false
ports: ports:
- "8014:8000" - "8014:8000"
volumes: volumes:
- ./kaopeilian-backend/app:/app/app # 代码热重载 - ./kaopeilian-backend/app:/app/app
- /data/prod-envs/uploads-fw:/app/uploads - /data/prod-envs/uploads-fw:/app/uploads
- /data/prod-envs/logs-fw:/app/logs - /data/prod-envs/logs-fw:/app/logs
- /data/prod-envs/secrets:/app/secrets:ro - /data/prod-envs/secrets:/app/secrets:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
networks: networks:
- prod-network - prod-network
- kaopeilian-network - kaopeilian-network
@@ -508,6 +513,8 @@ services:
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- PYTHONPATH=/app - PYTHONPATH=/app
- WORKERS=4
- RELOAD=false
ports: ports:
- "8016:8000" - "8016:8000"
volumes: volumes:
@@ -517,7 +524,6 @@ services:
- /data/prod-envs/secrets:/app/secrets:ro - /data/prod-envs/secrets:/app/secrets:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
networks: networks:
- prod-network - prod-network
- kaopeilian-network - kaopeilian-network

View File

@@ -1,6 +1,6 @@
# 考培练系统 - 环境配置与部署指南 # 考培练系统 - 环境配置与部署指南
> 最后更新2026-01-28 > 最后更新2026-02-03
## 一、环境总览 ## 一、环境总览
@@ -156,7 +156,94 @@ npm run build
--- ---
## 六、容器管理 ## 六、对象存储MinIO
> 2026-02-03 新增,用于统一管理所有租户的文件存储
### 6.1 服务信息
| 项目 | 值 |
|------|-----|
| **容器名** | kaopeilian-minio |
| **API端口** | 9000 |
| **管理界面端口** | 9001 |
| **数据目录** | /data/minio/data |
| **网络** | prod-network |
### 6.2 访问方式
- **管理界面**: http://120.79.247.16:9001
- **API端点**: http://kaopeilian-minio:9000容器内
- **用户名**: `kaopeilian_admin`
- **密码**: `KplMinio2026!@#`
### 6.3 Bucket列表
| Bucket名称 | 租户 | 说明 |
|-----------|------|------|
| kpl-ex | 恩喜成都总院 | 生产环境 |
| kpl-hua | 华尔倍丽 | 生产环境 |
| kpl-yy | 杨扬宠物 | 生产环境 |
| kpl-hl | 武汉禾丽 | 生产环境 |
| kpl-xy | 芯颜定制 | 生产环境 |
| kpl-fw | 飞沃 | 生产环境 |
| kpl-cxw | 崔曦文 | 生产环境 |
| kpl-demo | 演示环境 | 预生产 |
| kpl-kpl | KPL测试 | 测试环境 |
| kpl-peilian | 陪练项目 | 其他项目 |
### 6.4 后端配置
`.env` 或环境变量中设置:
```bash
# MinIO配置
MINIO_ENABLED=true
MINIO_ENDPOINT=kaopeilian-minio:9000
MINIO_ACCESS_KEY=kaopeilian_admin
MINIO_SECRET_KEY=KplMinio2026!@#
MINIO_SECURE=false
```
### 6.5 常用命令
```bash
# SSH登录服务器后
# 查看MinIO状态
docker ps | grep minio
# 查看MinIO日志
docker logs kaopeilian-minio --tail 50
# 使用mc客户端操作
mc ls kpl/ # 列出所有bucket
mc ls kpl/kpl-ex/ # 列出ex租户文件
mc cp file.pdf kpl/kpl-ex/ # 上传文件
mc rm kpl/kpl-ex/file.pdf # 删除文件
```
### 6.6 架构说明
```
用户上传文件
后端 storage_service
MinIO对象存储持久化+ 本地缓存(加速预览)
Nginx代理 → 用户下载
```
**特性**
- 自动降级MinIO不可用时自动使用本地存储
- URL兼容保持 `/static/uploads/` 格式,前端无需改动
- 智能缓存:文件自动下载到本地缓存用于预览/分析
- 多租户隔离每个租户独立Bucket
---
## 七、容器管理
### 当前运行容器统计 ### 当前运行容器统计
@@ -166,8 +253,9 @@ npm run build
| 后端容器 | 11 | | 后端容器 | 11 |
| Redis | 10 | | Redis | 10 |
| MySQL | 4 | | MySQL | 4 |
| MinIO | 1 |
| Nginx | 1 | | Nginx | 1 |
| **总计** | **37** | | **总计** | **38** |
### 查看所有容器 ### 查看所有容器
@@ -177,7 +265,7 @@ docker ps --format 'table {{.Names}}\t{{.Status}}'
--- ---
## 、测试账户 ## 、测试账户
| 角色 | 用户名 | 密码 | | 角色 | 用户名 | 密码 |
|------|--------|------| |------|--------|------|
@@ -187,7 +275,7 @@ docker ps --format 'table {{.Names}}\t{{.Status}}'
--- ---
## 、注意事项 ## 、注意事项
1. **前端共享**:所有生产租户共享同一套前端代码,编译一次全部更新 1. **前端共享**:所有生产租户共享同一套前端代码,编译一次全部更新
2. **后端独立**:每个租户有独立的后端容器和数据库 2. **后端独立**:每个租户有独立的后端容器和数据库
@@ -198,7 +286,7 @@ docker ps --format 'table {{.Names}}\t{{.Status}}'
--- ---
## 、Git 仓库配置 ## 、Git 仓库配置
```bash ```bash
# 查看远程仓库 # 查看远程仓库

View File

@@ -167,6 +167,8 @@ export interface CreateGrowthPathNode {
is_required: boolean is_required: boolean
prerequisites?: number[] prerequisites?: number[]
estimated_days: number estimated_days: number
position_x?: number // 画布X坐标
position_y?: number // 画布Y坐标
} }
// 创建成长路径请求 // 创建成长路径请求

File diff suppressed because it is too large Load Diff