Files
fquiz/memory/2026-04-12.md
T
2026-04-12 23:00:19 +08:00

217 lines
12 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.
# 2026-04-12
## 背景
- GitHub Actions 构建 API 镜像时,`pip install -r requirements.txt` 多次出现 `ReadTimeoutError``files.pythonhosted.org` / `pypi.org`)。
## 改动
- 更新 `api/Dockerfile`
- 新增构建参数 `PIP_INDEX_URL`(默认 `https://pypi.org/simple`)。
- 新增构建参数 `PIP_DEFAULT_TIMEOUT`(默认 `120`)。
- `pip install` 增加 `--retries 8 --timeout "${PIP_DEFAULT_TIMEOUT}" -i "${PIP_INDEX_URL}"`
- 设置 `PIP_DISABLE_PIP_VERSION_CHECK=1`
- 更新 `.github/workflows/main.yml`
- API 镜像构建新增 `build-args`
- `PIP_INDEX_URL=${{ secrets.PIP_INDEX_URL || vars.PIP_INDEX_URL || 'https://pypi.org/simple' }}`
- `PIP_DEFAULT_TIMEOUT=${{ vars.PIP_DEFAULT_TIMEOUT || '120' }}`
- 更新 `api/requirements.txt`
- 从宽范围约束改为精确版本,减少 pip 解析回溯与重复下载。
## 验证
- 本地触发 `docker build -f api/Dockerfile api --build-arg PIP_INDEX_URL=https://pypi.org/simple --build-arg PIP_DEFAULT_TIMEOUT=120`
- 日志确认:
- 新的 `pip install` 参数生效。
- 发生网络超时时会触发 `Retry(total=...)` 重试逻辑,而非首次超时直接失败。
## 风险与备注
- 受网络质量影响,构建时长可能显著增加。
- 若目标环境访问 `pypi.org` 不稳定,需在 GitHub Secrets/Variables 配置更近的 `PIP_INDEX_URL`
## 追加修正(同日)
- 触发问题:`No matching distribution found for websockets>=10.4; extra == "standard"`
- 原因:`uvicorn[standard]` 会强依赖 `websockets`;在当前包源下解析失败。
- 处理:
- `api/requirements.txt``uvicorn[standard]==0.44.0` 改为 `uvicorn==0.44.0`,并新增 `wsproto==1.3.2`
- `api/Dockerfile` 启动命令追加 `--ws wsproto`,明确 WebSocket 协议实现。
- 结果:构建日志不再出现 `websockets>=10.4` 依赖链,改为安装 `wsproto`
## 追加修正(部署脚本 YAML 解析)
- 触发问题:部署阶段 `docker compose` 报错 `yaml: line 2: mapping values are not allowed in this context`,并伴随 `DRONE_SSH_PREV_COMMAND_EXIT_CODE` 变量告警。
- 原因:`appleboy/ssh-action``script_stop: true` 下会插入额外控制逻辑,和脚本中的 heredoc 组合后可能污染生成文件。
- 处理:移除 workflow 中 `appleboy/ssh-action``script_stop: true`,保留脚本内 `set -euo pipefail` 作为失败中断机制。
## 追加修正(API 构建超时 + 解析冲突)
- 触发问题:
- `pip` 拉取 `files.pythonhosted.org` 频繁 `ReadTimeoutError`
- 在高延迟场景下,解析 `pydantic` 版本链时出现 `ResolutionImpossible`
- 处理:
- `api/requirements.txt` 新增显式锁定:
- `pydantic==2.12.5`
- `pydantic-core==2.41.5`
-`psycopg[binary]==3.3.3` 改为显式双包:
- `psycopg==3.3.3`
- `psycopg-binary==3.3.3`
- 验证:
- `docker compose build api --no-cache` 成功。
- 日志显示 `pydantic-core-2.41.5``psycopg-binary-3.3.3` 均成功下载并安装,最终 `fquiz-api Built`
## 追加开发(文件管理一期主链)
- 目标:
- 在现有 FastAPI + SQLAlchemy + RBAC + Next.js App Router 结构上落地文件管理一期最小闭环。
- 支持 VFS / S3 driver 抽象,先打通后台 `/admin/files` 骨架与核心目录操作。
- 后端改动:
- 新增模型:`file_storage_backends``file_storage_mounts``file_index_entries``api/app/models/file_storage.py`)。
- `init_db` 增加 `file_storage` 模型加载,保证 `create_all` 生效。
- 新增存储驱动层:`VfsStorageDriver``S3StorageDriver` 与统一工厂(`api/app/services/storage_driver.py`)。
- 新增文件服务:目录列表(带索引同步)、创建目录、删除路径(`api/app/services/file_service.py`)。
- 新增 API`/api/v1/admin/files``/directories``/delete``api/app/api/v1/admin_files.py`)。
- 种子数据增加 `file.read`/`file.manage` 权限、`admin.files` 菜单、默认 VFS backend+mount 与 S3 backend 占位配置。
- 前端改动:
- 新增后台页面:`web/src/app/admin/files/page.tsx`
- 提供挂载点切换、面包屑目录浏览、刷新、新建目录、删除。
- 增补类型定义 `FileListResponse` 等,并在后台首页新增文件管理入口卡片。
- 依赖与配置:
- `api/requirements.txt` 增加 `boto3==1.40.59`S3 driver 依赖)。
- `.env.example` 增加 `FILE_VFS_ROOT`(默认 `./data/vfs`)。
- 最小验证:
- `python3 -m compileall` 覆盖新增/改动后端文件通过。
- `npx eslint`(目标前端文件)通过。
- `npx tsc --noEmit`web)通过。
- 风险与缺口:
- 当前未提供存储后端/挂载点的可视化配置管理,S3 仍需手工写入 backend 配置并启用。
- 仅实现目录浏览+建目录+删除,未包含上传、下载、移动、重命名、分享、回收站等完整网盘能力。
## 追加修复(API 容器 unhealthy + Docker Hub 超时)
- 触发问题:
- `docker compose up` 阶段 `fquiz-api` 持续 unhealthy(容器反复重启)。
- 部署日志中 `db` 拉取 `registry-1.docker.io` 超时(`Client.Timeout exceeded while awaiting headers`)。
- 根因:
- `api/app/services/admin_service.py` 在模块导入期构建 `selectinload(...)` 常量,提前触发 SQLAlchemy mapper 配置;当时 `User.audit_logs -> "AuditLog"` 对应模型尚未注册,导致启动即崩溃。
- 生产部署 compose 固定 `postgres:16-alpine`,对 Docker Hub 可达性强依赖。
- 处理:
- `api/app/models/__init__.py` 增加模型模块统一导入,保证关系类可解析。
- `api/app/services/admin_service.py``selectinload(...)` 由模块级常量改为惰性构建,避免导入阶段触发 mapper 配置。
- `.github/workflows/main.yml` 部署生成的 `docker-compose.prod.yml` 中 DB 镜像改为可配置:
- `image: ${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine}`
- `.env` 模板新增 `POSTGRES_IMAGE` 默认值。
- 验证:
- 使用镜像源参数重建 API 成功:
- `docker compose build api --build-arg PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple --build-arg PIP_DEFAULT_TIMEOUT=600 --build-arg PIP_RETRIES=30`
- `docker compose up -d api` 后状态为 `Up ... (healthy)`
- `curl http://127.0.0.1:8000/health` 返回 `{"status":"ok","service":"fquiz-api","version":"0.1.0"}`
## 追加修复(GitHub 发布 Run Command Timeout
- 触发问题:
- 发布阶段日志持续停留在 `docker compose pull` 的 layer 下载进度。
- `appleboy/ssh-action` 最终报错:`Run Command Timeout`,作业退出码 `1`
- 根因:
- 远端拉取镜像速度慢时,SSH Action 的命令执行超时先触发,未等到 `docker compose pull` 自然完成。
- 处理:
- 更新 `.github/workflows/main.yml` 部署步骤:
-`appleboy/ssh-action` 增加 `timeout: 120s``command_timeout: 45m`
- 脚本内新增 `DOCKER_CLIENT_TIMEOUT=600``COMPOSE_HTTP_TIMEOUT=600` 默认值。
- 新增 `pull_with_retry`(最多 3 次)包装 `docker compose pull`,网络抖动时自动重试。
- 验证建议:
- 推送触发 `main` 发布,观察部署日志不再在固定时长点报 `Run Command Timeout`
- 远端 `docker compose ps` 应显示 `db/api/web` 均为 `Up`(或 `healthy`)。
## 追加改造(后台视觉风格方案 A)
- 目标:
- 将后台从默认黑白风格升级为更现代的 `Slate + Cyan` 视觉基线,保持业务逻辑不变。
- 处理:
- `web/src/app/layout.tsx`
- 字体切换为 `Space Grotesk`(标题)+ `Manrope`(正文)+ `JetBrains Mono`(等宽)。
- `web/src/app/globals.css`
- 新增统一设计 token(背景/边框/强调色/文本层级)。
- 新增通用样式类:`surface-card``surface-card-muted``notice``btn-*``control``table-*`
- 增加浅色渐变背景与柔和光斑,提升整体层次。
- `web/src/app/admin/layout.tsx`
- 后台侧栏、顶部标题区改为半透明磨砂卡片风格,激活态采用青色高亮。
- `web/src/app/admin/**``web/src/app/page.tsx`
- 统一替换卡片/按钮/表单/表格样式到新通用类,保持页面结构与交互逻辑不变。
- 验证:
- `npm run lint:web` 通过。
- `npm run build:web` 失败(环境问题,非本次样式改动引入):
- Turbopack 报 `Can't resolve '@tanstack/react-query'` / `Can't resolve 'react'`,但 `npm --workspace web ls react @tanstack/react-query --depth=0` 可见依赖存在。
- 风险:
- 本次改动覆盖前端多个后台页面,主要风险是视觉回归与局部间距细节,需要联调页面人工验收。
## 追加修复(DB 端口冲突 + pgvector 基线)
- 触发问题:
- 远端启动 `db` 报错:`listen tcp4 0.0.0.0:5432: bind: address already in use`
- 服务器已有旧 PostgreSQL 占用 `5432`,当前容器无法绑定。
- 处理:
- `docker-compose.yml`
- DB 端口映射改为 `${POSTGRES_PORT:-5433}:5432`
- DB 默认镜像改为 `docker.m.daocloud.io/pgvector/pgvector:pg16`
- `.github/workflows/main.yml`
- 生产 compose 模板同步改为 `${POSTGRES_PORT:-5433}:5432`
- `.env` 自动模板新增 `POSTGRES_PORT=5433`
- `POSTGRES_IMAGE` 默认改为 `docker.m.daocloud.io/pgvector/pgvector:pg16`
- `.env.example`
- 新增 `POSTGRES_PORT=5433`,并同步默认 `POSTGRES_IMAGE` 为 pgvector 镜像。
- `README.md`
- PostgreSQL 默认访问端口更新为 `5433`,并注明可通过 `POSTGRES_PORT` 覆盖。
- 验证:
- `docker compose config` 通过。
- 展开结果确认:
- `db.ports.published``5433`
- `db.image``docker.m.daocloud.io/pgvector/pgvector:pg16`
## 追加优化(后台页面左右留白收敛)
- 背景:
- 用户反馈后台页面左右两侧 margin 偏大,宽屏下可用内容空间浪费。
- 处理:
- `web/src/app/admin/layout.tsx` 将主容器最大宽度由 `max-w-[1360px]` 提升到 `max-w-[1760px]`
- 同时增加轻量响应式外层横向内边距(`px-3 sm:px-4 xl:px-6`),避免贴边。
- 主内容区内边距从 `p-6 md:p-8` 收敛为 `p-4 md:p-6`,进一步释放表格可视宽度。
- 验证:
- `cd web && npx eslint src/app/admin/layout.tsx` 通过。
## 追加修复(CORS 跨域配置增强)
- 触发问题:
- 前端调用 API 出现浏览器 CORS 拦截,需要支持更灵活的来源配置。
- 处理:
- 后端 CORS 配置增强(`api/app/core/config.py` + `api/app/main.py`):
- 新增 `API_CORS_ORIGIN_REGEX` 配置项(可选)。
- `API_CORS_ORIGINS` 支持 `*` 与通配符域名(如 `https://*.example.com`),内部转换为正则匹配。
- `CORSMiddleware` 增加 `allow_origin_regex=settings.cors_origin_regex`
- 部署与环境模板同步:
- `.env.example` 新增 `API_CORS_ORIGIN_REGEX=`
- `docker-compose.yml` API 环境变量新增 `API_CORS_ORIGIN_REGEX` 透传。
- `.github/workflows/main.yml` 生产 compose 模板与默认 `.env` 模板同步新增该变量。
- 文档补充:
- `README.md` 增加 CORS 配置说明与示例。
- 验证:
- `python3 -m compileall api/app/core/config.py api/app/main.py` 通过。
- `docker compose config` 展开结果包含:
- `API_CORS_ORIGINS`
- `API_CORS_ORIGIN_REGEX`
## 追加修复(Web 镜像构建 TypeScript 阻塞)
- 触发问题:
- `docker compose build web``web/Dockerfile``RUN npm run build` 失败。
- 报错定位在 `web/src/app/page.tsx:190` 附近(`placeholder="Email"` 上下文),实际错误为 `Cannot find name 'title'`
- 根因:
- 登录/注册表单标题渲染使用了未定义变量 `title`,导致 TypeScript 检查失败并中断 Next.js 构建。
- 处理:
- `web/src/app/page.tsx``{title}` 改为基于 `mode` 的内联表达式:
- 登录显示 `登录`
- 注册显示 `注册`
- 验证:
- 复跑 `docker compose build web`,构建成功,路由静态生成完成,最终输出 `fquiz-web Built`