面向实验室场景的试剂与耗材管理系统,覆盖申购、审批、到货、入库、借用、归还、公告、设备会话管理和受控自动化入口。项目采用 FastAPI + React 前后端分离架构,适合单实验室或中小团队快速部署,也适合通过 CLI、Agent skill、MCP、智能机器人和浏览器插件接入日常流程。
- 试剂与耗材双流程管理 试剂支持 CAS 号、到货确认、一键入库;耗材支持独立订单流程与完成态管理。
- CAS 防重与库存预警 申购时可按 CAS 查询现有库存和历史订单,降低重复购买概率。
- 试剂品牌主数据 试剂订单、库存和待入库表单共享品牌选项,支持拼音搜索、软删除和操作日志记录。
- 中文拼音检索与 FTS 搜索 名称、分类、品牌、位置等字段会预计算拼音,并结合 SQLite FTS5 提供搜索能力。
- 库存借还闭环 支持借出、归还、借用历史、当前借用人、临时保管人等字段;历史记录缺少规格时可在归还时补齐。
- 仪表盘与管理看板 个人模式展示订单、借用和待入库待办;成员看板展示公告、运行统计、个人待处理、订单概览、近期到货/入库和库存告警;管理员管理模式展示全局待办、风险提醒、库存告警和管理表格;公用账户展示公告和全局窗口统计;低库存按剩余比例低于 10% 判断。
- 常用货架与常用试剂 支持常用货架分组、CAS 主数据、位置统计、加瓶和扣减。
- 用户、设备、会话 支持 HttpOnly Cookie 登录、设备列表、批量注销、IP/设备数量限制。
- 公告与图片上传
公告支持图片,文件保存到
static/运行目录,经/static/访问;数据库只保留 URL,并受尺寸、类型、上传频率控制。 - 化学结构检索 可选启用本地结构缓存、PubChem 解析、Ketcher 绘制和子结构检索。
- CLI、Agent skill 与脚本入口
提供
python -m lsm_cli命令行入口,供脚本、Agent skill 和无 UI 场景在受控命令面内操作。 - MCP、智能机器人与浏览器插件 MCP 通过 CLI 子进程工作;企业微信智能机器人、微信客服和浏览器插件都回到标准 API 与确认流程,识别到 CAS 时优先执行系统内精确查询。
| 层级 | 技术 |
|---|---|
| 后端 | FastAPI, SQLModel, SQLite, Redis, python-jose, bcrypt, Pillow, pypinyin |
| 前端 | React 19, TypeScript 5.9, Vite 8, React Router 7, Zustand, React Hook Form, Valibot |
| UI | Radix UI, Tailwind CSS 4, Lucide React, Framer Motion |
| 表格与数据 | TanStack Table 8, TanStack Virtual, TanStack Query 5, Axios |
| 化学相关 | RDKit, Ketcher, PubChem 解析, 本地结构缓存 |
| 自动化入口 | lsm_cli, Agent skill, lsm_mcp, 企业微信智能机器人, 微信客服, 浏览器插件 |
| 构建与校验 | Poetry, npm, ruff, ESLint, TypeScript build |
| 部署 | Docker Compose, Nginx, Uvicorn |
本地环境:
- Python 3.11+
- Node.js 20+
- npm 10+
- Redis 6+ 或 7+(本地开发可选,但推荐开启)
git clone <your-repo-url> LabStorageManager
cd LabStorageManager推荐使用 Poetry:
poetry installcd frontend
npm install
cd ..以 .env.example 为模板准备本地运行配置。该配置文件只用于当前环境,不纳入版本库。
PowerShell:
Copy-Item .env.example .envBash:
cp .env.example .env最少需要确认这些字段:
| 变量 | 说明 |
|---|---|
ENV |
本地开发使用 development |
CORS_ORIGINS |
前端地址白名单,开发时通常为 http://localhost:5173 |
DEFAULT_ADMIN_PASSWORD |
必填;后端首次启动会用它初始化管理员 |
ALGORITHM |
默认 RS256 |
DATABASE_URL |
主库 SQLite 连接串,必须指向文件型 SQLite;本地示例默认 sqlite:///./lab_inventory.db,Docker Compose 默认写入 /data/lab_inventory.db |
QUERY_LOG_DIR |
搜索日志库目录 |
开发环境配置:
- 将
ENV=development - 将
CORS_ORIGINS改为当前前端地址 - 首次启动前设置
DEFAULT_ADMIN_PASSWORD
说明:
- 当
ENV=development且本地还没有 RSA 密钥时,后端可自动生成临时密钥对。 - 当
ENV=production时,ALGORITHM必须为RS256,并且 RSA 私钥与公钥路径必须可用。
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000启动后可访问:
- 开发文档:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - 健康检查:
http://localhost:8000/health
注意:
- 只有开发模式会暴露
/docs、/redoc和/openapi.json。 - 首次启动会自动初始化至少一个管理员账户。
cd frontend
npm run dev -- --port 5173 --strictPort前端地址:
http://localhost:5173
当前本地开发仅放行 5173 端口,建议使用上述命令启动。--port 5173 指定端口,--strictPort 会在端口被占用时直接报错退出,避免 Vite 自动切换到 5174 等未放行端口。若启动提示端口占用,请先停止占用 5173 的进程,再重新启动。
浏览器访问地址应与后端 CORS_ORIGINS 中的来源一致(协议、主机名和端口均需匹配);localhost 与 127.0.0.1 属于不同来源。
| 变量 | 示例 | 说明 |
|---|---|---|
ENV |
development |
development/dev 或 production |
CORS_ORIGINS |
["http://localhost:5173"] |
JSON 数组字符串 |
DEFAULT_ADMIN_PASSWORD |
your-password |
默认管理员密码,未设置将导致启动失败 |
ALGORITHM |
RS256 |
生产环境必须使用 RS256 |
| 变量 | 示例值 | 说明 |
|---|---|---|
DATABASE_URL |
sqlite:///./lab_inventory.db |
主库 SQLite 连接串;本地默认写入项目根目录,Docker Compose 默认覆盖为 sqlite:////data/lab_inventory.db |
QUERY_LOG_DIR |
logs |
搜索日志库目录;Docker Compose 默认覆盖为 /data/logs |
TRUST_PROXY_HEADERS |
true |
Compose 生产部署设为 true;本地开发或无可信反代时设为 false |
CACHE_VERSION |
1.1.0 |
前后端缓存失效版本;留空时使用 APP_VERSION |
ACCESS_TOKEN_EXPIRE_MINUTES |
10080 |
登录态默认 7 天 |
SESSION_EXPIRE_HOURS |
168 |
会话有效期,默认 7 天 |
MAX_IP_PER_USER |
5 |
兼容旧配置,当前不限制登录 IP 数量 |
MAX_DEVICE_PER_USER |
10 |
每个用户允许的最大设备会话数;超限时淘汰最旧会话 |
CLI_RATE_LIMIT_COUNT |
60 |
CLI API 单窗口请求次数 |
CLI_RATE_LIMIT_WINDOW_SECONDS |
60 |
CLI API 限流窗口秒数 |
CLI_LOGIN_RATE_LIMIT_COUNT |
3 |
CLI 登录单窗口尝试次数 |
CLI_LOGIN_RATE_LIMIT_WINDOW_SECONDS |
300 |
CLI 登录限流窗口秒数 |
REDIS_HOST |
127.0.0.1 |
Redis 主机 |
REDIS_PORT |
6379 |
Redis 端口 |
REDIS_DB |
1 |
Redis 逻辑库 |
REDIS_PASSWORD |
空 | Redis 仅监听本机时可留空;对外监听时必须设置 |
REDIS_KEY_PREFIX |
lsm |
Redis key 前缀 |
REDIS_MAX_CONNECTIONS |
100 |
单个后端进程的 Redis 连接池上限 |
| 变量 | 示例值 | 说明 |
|---|---|---|
ARCHIVE_SCHEDULER_ENABLED |
true |
启用后端内置日志归档调度 |
ARCHIVE_RUN_AT_TIME |
03:30 |
按服务器本地系统时间执行归档 |
ARCHIVE_RUN_WEEKDAY |
sun |
设置后每周指定星期执行;留空则每天执行 |
ARCHIVE_RUN_DAY |
1 |
设置后每月指定日期执行,与 ARCHIVE_RUN_WEEKDAY 互斥;短月份取最后一天 |
ARCHIVE_INTERVAL_HOURS |
168 |
未设置固定时间时使用的周期小时数 |
ARCHIVE_STARTUP_DELAY_SECONDS |
300 |
未设置固定时间时首次运行前的延迟 |
ARCHIVE_OUTPUT_DIR |
logs |
归档库输出目录 |
统一通过 MAINTENANCE_MODE 设置模式:off 为正常模式,on 为库存只读,full 为完全维护页面。修改后重启后端并刷新页面;首次使用需要部署包含此功能的前端。
- 保留登录、账户切换、库存搜索/筛选、分页、详情、结构式展示和导出,沿用原有认证权限。
- 后端仅放行明确允许的查询和认证接口;借用、归还、编辑、新建、删除、导入及其他业务接口均不可用,管理员也不例外。
on和full均拒绝 CLI 访问业务 API,并返回明确维护提示;CLI 的 GitHub 更新检查与安装命令不访问业务 API,仍可运行。- 关闭输入建议和结构检索;结构式仅从快照缓存读取并在前端渲染,不触发后端外部解析。暂停搜索日志、查询记忆、反馈、偏好、补全索引、自动归档和数据库初始化/升级任务。
- 不写应用请求日志、审计日志和用户操作日志;异常保留控制台输出。反向代理和进程管理器的日志由各自配置控制。
- 登录、退出及会话验证仍需维护会话和安全限流状态,因此不是数据库文件级只读模式。主库必须已初始化且可用,不能用于正在执行不兼容表结构迁移的数据库。
设置 MAINTENANCE_MODE=off 并重启可恢复正常模式。兼容旧值 false(对应 off)和 true(对应 full)。full 延续原有的前端维护页行为;数据库升级期间若需完全隔离 API,应同时在网关关闭访问。
需要在主库升级期间继续查询时,使用独立快照服务:
# 1. 先将主服务切到 MAINTENANCE_MODE=on 并重启,冻结业务写入
# 2. 使用 SQLite Backup API 生成并校验一致性快照
poetry run python -m app.read_only.snapshot create
poetry run python -m app.read_only.snapshot verify
# 3. 在独立端口启动只读服务,再由 Nginx 将后端流量切到该端口
poetry run python -m uvicorn app.read_only.main:app --host 127.0.0.1 --port 8001只读服务仅注册运行状态、登录/退出、当前用户、库存列表/详情、导出和结构式缓存读取。它不提供输入建议、CAS 重复检查、借用历史或结构检索。服务使用 mode=ro&immutable=1 打开快照,不导入 app.main,不创建表、不迁移、不启动后台任务,也不写搜索日志、用户操作日志或建议反馈。维护登录签发专用短期令牌,会话只写 Redis,不写快照;退出会立即撤销该会话,令牌也不能作为正常服务会话使用。生产环境 Redis 不可用时登录按安全策略返回 503。
快照元数据保存在同名 .db.json 文件,包含创建时间、行数和 SHA-256。前端会显示“数据截至”时间。每次进入维护前重新执行 create,文件通过临时文件校验后原子替换;不要直接复制正在使用的 SQLite 主库文件。
恢复顺序:完成主库升级和检查,启动 MAINTENANCE_MODE=off 的主服务,将 Nginx 切回主服务,再停止只读服务。快照绝不回写主库。
| 变量 | 示例值 | 说明 |
|---|---|---|
MAX_FILE_SIZE_MB |
10 |
单文件体积限制 |
MAX_UPLOAD_REQUEST_SIZE_MB |
12 |
单次请求总上传限制;生产 Nginx 需保持一致 |
ALLOWED_IMAGE_TYPES |
["image/jpeg", "image/png", "image/webp"] |
JSON 数组字符串 |
MAX_IMAGE_WIDTH |
800 |
最大宽度 |
MAX_IMAGE_HEIGHT |
800 |
最大高度 |
MAX_IMAGE_SIZE_KB |
100 |
压缩后图片大小上限 |
UPLOAD_RATE_LIMIT_COUNT |
10 |
上传限流次数 |
UPLOAD_RATE_LIMIT_WINDOW_SECONDS |
300 |
上传限流时间窗口 |
| 变量 | 示例值 | 说明 |
|---|---|---|
MAX_TOTAL_ANNOUNCEMENTS |
10 |
单管理员公告总数上限 |
MAX_VISIBLE_ANNOUNCEMENTS |
5 |
单管理员可见公告数上限 |
NIUTRANS_APPID |
空 | 牛翻 API AppID;未配置时不启用 |
NIUTRANS_APIKEY |
空 | 牛翻 API Key;未配置时不启用 |
| 变量 | 示例值 | 说明 |
|---|---|---|
CHEM_STRUCTURE_FEATURE_ENABLED |
true |
是否启用结构缓存和子结构检索接口 |
CHEM_RESOLVER_PUBCHEM_ENABLED |
true |
是否允许通过 PubChem 解析 CAS 结构 |
CHEM_PUBCHEM_RATE_LIMIT_PER_SECOND |
2 |
PubChem 请求速率上限 |
CHEM_PUBCHEM_TIMEOUT_SECONDS |
20 |
PubChem 请求超时 |
CHEM_PUBCHEM_MAX_RETRIES |
3 |
管理员显式解析和离线回填的请求内重试次数 |
CHEM_PUBCHEM_USER_AGENT |
LabStorageManager/1.1.0 |
发送到 PubChem 的 User-Agent |
CHEM_RESOLUTION_SCHEDULER_ENABLED |
true |
启用 SQLite 持久化自动解析队列 |
CHEM_RESOLUTION_RETRY_DELAYS_SECONDS |
[60,300,1800] |
首次失败后的 3 次自动重试间隔 |
CHEM_RESOLUTION_JOB_LEASE_SECONDS |
120 |
自动解析任务租约;过期后允许其他进程恢复 |
CHEM_RESOLUTION_JOB_ATTEMPT_TIMEOUT_SECONDS |
1800 |
单次自动解析硬超时;超时按可重试错误持久化 |
CHEM_STRUCTURE_SEARCH_MAX_RESULTS |
100 |
子结构检索默认结果上限 |
CHEM_STRUCTURE_INDEX_SNAPSHOT_PATH |
.cache/structure-index.snapshot.json |
带数据库代际、revision、RDKit 版本和 checksum 的持久快照 |
CHEM_STRUCTURE_INDEX_MAINTENANCE_HOUR |
3 |
每天按 DISPLAY_UTC_OFFSET 判断是否整理的小时(0~23) |
CHEM_STRUCTURE_INDEX_WEEKLY_MAINTENANCE_WEEKDAY |
6 |
未达阈值时的每周整理日(0=周一,6=周日) |
| 变量 | 说明 |
|---|---|
PRIVATE_KEY_PATH |
RS256 私钥路径 |
PUBLIC_KEY_PATH |
RS256 公钥路径 |
SECRET_KEY |
仅 HS256 时使用;开发环境可自动生成临时值 |
# 开发启动
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# 代码检查
ruff check app/cd frontend
# 开发启动(固定 5173,端口占用时直接退出)
npm run dev -- --port 5173 --strictPort
# 生成带版本号的 RDKit 和本地字体资源映射
npm run generate:static-assets
# Lint
npm run lint
# 生产构建
npm run build# 构建并启动
APP_PORT=80 docker compose up -d --build
# 查看状态
docker compose ps
# 查看日志
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f redisCompose 默认把主库、搜索日志库、上传文件和密钥放在持久化 volume 中。备份 SQLite 主库前先做 checkpoint,并保证主库及其 WAL/SHM 伴随文件处于同一备份批次。
Browser / Browser Extension
|
v
React 19 + Vite + Axios
|
v
FastAPI
|- Auth / Session
|- Dashboard
|- Inventory
|- Reagent Orders
|- Consumable Orders
|- Announcements
|- Event Stream
|
v
SQLite (WAL) + Redis + /static/
WeCom / WeChat KF
|
v
lsm_mcp -> python -m lsm_cli -> FastAPI API
- 使用
FastAPI暴露 API,开发模式下提供 Swagger/ReDoc。 SQLite在每个连接上都会显式开启PRAGMA journal_mode=WAL。- 数据库初始化由
app/database.py编排,app/db_bootstrap/负责 schema 补齐、结构绑定回填、性能索引、FTS 表、触发器和一致性检查。 - 全局中间件处理以下问题:
- 请求日志与
X-Request-ID - 上传请求体积限制
- 生产环境 HTTPS 跳转
- Cookie 鉴权下的 CSRF Origin/Referer 校验
- 安全响应头与 CSP/HSTS
- 请求日志与
- 使用
BrowserRouter管理路由。 - 登录态通过 HttpOnly Cookie 持有,Axios 统一开启
withCredentials。 - 页面或模块之间多采用懒加载策略。
- 登录接口写入 Cookie,不依赖浏览器
localStorage保存 token。 - 支持多设备登录与会话列表管理。
- 会话可按设备名称、IP 等信息追踪。
- 401 会统一触发前端登出与跳转。
| Key | 用途 |
|---|---|
app-ui |
主题、字体来源、仪表盘页签与模式偏好、公告已读/关闭、Bug 按钮隐藏 |
app-table |
表格 expandAll、fuzzySearch、matchMode、列宽 |
app-auth-meta |
设备 id/name、remembered user |
runtime-time-config |
后端返回的展示时区和 UTC 偏移 |
auth-storage |
Zustand 登录态持久化,带 TTL |
sidebar-storage |
Zustand 侧栏状态持久化,带 TTL |
chemical_properties_cache |
化学属性缓存,独立长 TTL |
cart_import_batch_latest |
浏览器插件导入桥接批次,2 小时 TTL |
inventory、reagent_order、consumable_order、users、chemical_name_map、log_timeline建有 SQLite FTS5 虚表。- 名称、拼音、拼音首字母等字段会被索引,便于中文检索。
- 大量列表查询配套了状态、申请人、时间、公共货架等复合索引。
.
├── app/ # FastAPI 后端
├── frontend/ # React 前端
├── browser-extension/ # 浏览器插件,用于购物车导入等场景
├── docker/ # Dockerfile、Nginx、入口脚本
├── lsm_cli/ # 本地命令行客户端
├── lsm_mcp/ # 受控 MCP 工具服务
├── robot/ # 企业微信智能机器人与微信客服入口
├── static/ # 上传图片与静态文件运行目录
├── wiki/ # VitePress 知识库源码
├── docker-compose.yml # 一体化部署编排
├── pyproject.toml # 后端依赖与工具配置
└── README.md
app/
├── main.py # FastAPI 入口、中间件、路由装配
├── database.py # SQLModel 引擎、WAL 与初始化编排
├── db_bootstrap/ # SQLite schema、索引、FTS 与一致性检查
├── api/ # 路由层
├── core/ # 配置、认证、常量、请求工具
├── models/ # SQLModel 数据模型
└── services/ # 业务服务
frontend/src/
├── api/ # Axios API 封装
├── components/ # UI 组件、业务组件、结构检索与日志详情组件
├── hooks/ # 自定义 hooks
├── lib/ # 工具、常量、校验、品牌选项与本地存储
├── pages/ # 页面级组件
└── store/ # Zustand 状态管理
这是当前仓库最直接的部署方式。
包含的服务:
frontendbackendredis
步骤:
git clone <your-repo-url> LabStorageManager
cd LabStorageManager
Copy-Item .env.example .env至少修改:
DEFAULT_ADMIN_PASSWORDCORS_ORIGINSENV
如果 Redis 仅监听 127.0.0.1 或 Compose 内网,可让 REDIS_PASSWORD 为空;Redis 对外监听或跨机器访问时必须设置强密码。
如果保持默认 ALGORITHM=RS256,首次启动前需要在 Compose 的持久化卷里生成 RSA 密钥:
docker compose run --rm --entrypoint sh backend -c \
'mkdir -p /data/keys && \
openssl genrsa -out /data/keys/private.pem 2048 && \
openssl rsa -in /data/keys/private.pem -pubout -out /data/keys/public.pem'后端启动时会把持久化密钥目录映射到应用内路径,并读取 PRIVATE_KEY_PATH 与 PUBLIC_KEY_PATH。
然后启动:
APP_PORT=80 docker compose up -d --build检查服务:
docker compose ps
curl http://127.0.0.1:${APP_PORT:-80}/
curl http://127.0.0.1:${APP_PORT:-80}/health说明:
- 前端镜像基于
node:20-alpine构建,运行层是安装 Nginx 与 Brotli 模块的 Alpine 镜像。 - 后端镜像基于
python:3.11-slim,使用uvicorn启动。 - Compose 会把 Redis 地址注入为容器内部服务名
redis。 - Compose 的
app_datavolume 挂载到容器/data,默认保存/data/lab_inventory.db、/data/logs、/data/static和/data/keys。
适合开发调试。
- 本地启动 Redis(推荐)
- 启动后端
uvicorn - 在
frontend目录执行npm run dev -- --port 5173 --strictPort启动前端 - 前端通过
CORS_ORIGINS与 API 基地址访问后端
适合服务器上由 Nginx 托管前端静态文件、后端由进程管理器启动的部署方式。
前端构建:
cd frontend
npm ci
npm run build将 frontend/dist 放到 Nginx 站点根目录。浏览器插件需要单独设置 browser-extension/.env 后执行 npm run build:extension。
后端安装与启动:
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
poetry install --without dev,scripts --no-root
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000当前仓库根 pyproject.toml 使用非包模式,生产运行应按 Poetry 依赖清单安装后直接启动 ASGI 应用。
仓库包含 browser-extension/,用于浏览器侧导入或购物车同步相关场景。后端提供了 /cart-import 路由,会将入口跳转到前端页面。可在chrome商店自行下载插件。
browser-extension/manifest.json 和 browser-extension/shared/generated-config.js 是构建期文件,不提交到 Git。生产部署浏览器插件前,先准备插件构建配置,再运行:
npm run build:extensionAgent skill、lsm_mcp/ 和 robot/ 都应通过 python -m lsm_cli 或安装后的 lsm 命令进入系统,不直接访问数据库。企业微信智能机器人和微信客服入口位于 robot/,查询和借还流程都通过 MCP、CLI 与后端 API 完成。
CLI 通过后端 API 工作,不直接访问数据库,详见 lsm_cli/README.md
将下面一句发送给 Claude Code 或 Codex,可以直接安装cli:
安装协议会从最新 GitHub Release 获取预编译 CLI。
本项目使用 Apache License 2.0。详见 LICENSE。
