# AGENTS.md 本仓库(Track 生产流转系统)的工作笔记。仅在验证过之后才写入,避免传谣。 > ## ⚠️ 本仓库是 **LICA 部门实例** > > 派生自 IRIS 实例(`git.iris-rs.cn/duxingchen/track.git` 的 > `feature/ai-audit-update` 分支 @ `192c8ee`)。两套系统在**同一台机器**上并行 > 运行、共用 MOM 主数据,但人员、物料、业务数据**完全隔离**。 > > | | IRIS 实例 | LICA 实例(本仓库) | > |---|---|---| > | 代码目录 | `/home/yueli/track` | `/home/yueli/track-lica` | > | 容器 | `track_db` / `track_backend` / `track_frontend` | `lica_db` / `lica_backend` / `lica_frontend` | > | compose 项目 | `track` | `track-lica` | > | PC 管理端 | 8010 | **8030** | > | 后端 API | 8011 | **8031** | > | 数据库 | 8012 | **8032** | > | 数据卷 | `track_pgdata` | `lica_pgdata` | > | 部门过滤 | `IRIS` | `LICA` | > > **两条红线:** > 1. **永远不要在 `/home/yueli/track` 目录下执行 `docker compose down -v`** —— > 那会删掉 IRIS 的生产数据。两个卷相互独立,在 `track-lica` 下执行不影响 IRIS。 > 2. 改后端地址时别只改一半:PC 端走 `docker-compose.yml` 的 `CORS_ORIGINS`, > 移动端走 `track-uniapp/src/utils/config.js`(uni-app 地址的**唯一来源**) > 和 `track-uniapp/.env`。只改一处会出现「接口通了但图片/上传 404」。 ## 架构速览 - `backend/` FastAPI + SQLAlchemy 2.x(async) + Alembic,PostgreSQL。 - `frontend/` React 19 + Vite + antd + Tailwind。路由见 `src/App.tsx`, 管理端菜单见 `src/components/layout/AdminLayout.tsx`(`MENU` 数组)。 - 登录不走 Track 自己的用户表,而是**只读** MOM(KCGL) 的 `sys_user`: `sys_user.username` 存 `"真实姓名/登录账号"`,`login()` 用 `WHERE username LIKE '%/<账号>'` 匹配,`display_name` 由 `/` 拆解得到。 MOM 连接配置在 `app/core/mom_database.py`(同步 psycopg2 引擎)。 - **组织隔离**:本实例只服务 LICA 部门,唯一开关是 `app/core/config.py` 的 `ORG_DEPARTMENT`(对应 MOM `sys_user.department`)与 `MATERIAL_CATEGORY_PREFIX`(对应 MOM `material_base.category` 前缀)。 过滤点共 4 处:登录 `auth_service.login`、人员列表 `endpoints/users.py`、 物料 `endpoints/materials.py` 的 `groups`/`items`、 人员操作统计 `dashboard_service.get_user_operations`。 - 人员与物料都是**服务端钉死**部门,客户端传什么部门参数都不采纳 (这样 uni-app 里写死的 `dept` 不会造成跨部门影响)。 - 刻意**不做**「查询失败退回全表」的降级 —— 那是跨部门数据泄漏, 宁可查不出,不可查过头。 - ⚠️ 物料必须用前缀 `category LIKE 'LICA/%'`,**不能用** `ILIKE '%LICA%'`: MOM 里存在 171 条 `IRIS/成品/LICA/...`(无人机/野外便携/高塔监测等), 模糊匹配会把 IRIS 的物料漏给 LICA。已实测前缀匹配命中 795 条 / 5 个分组。 ## 本地起环境(关键,踩过的坑都在这) 1. **本机没有 Postgres 时需要先装**(容器内 `sudo` 可用): `sudo -n apt-get install -y --fix-missing postgresql postgresql-contrib` 然后 `sudo -n pg_ctlcluster 17 main start`。 本仓库不使用 pgvector,无需额外扩展。 2. **数据库端口与生产默认值不同**,必须用环境变量覆盖: - `DATABASE_URL=postgresql+asyncpg://lica:<密码>@127.0.0.1:8032/lica_production` - `MOM_DB_HOST=inventory_db`、`MOM_DB_PORT=5432`(容器内走 projects_default 网络的服务名;从宿主机连则用 `127.0.0.1:5435`) - `SECRET_KEY=<≥32 字符>`:`DEBUG=false` 时配置项会**拒绝**默认 SECRET_KEY (见 `app/core/config.py` 的校验),不设会直接 import 失败。 3. 迁移:`cd backend && python3 -m alembic upgrade head`(没有全局 `alembic` 命令, 要用 `python3 -m alembic`)。校验纯 SQL 用 `alembic upgrade head --sql`。 4. 前端 proxy 指向 Docker 服务名 `lica_backend:8000`(本实例的服务名**刻意不叫** `backend`,避免在 `projects_default` 网络上与 IRIS 实例重名,否则将来任何一方 写 `http://backend:8000` 会随机打到另一个部门)。本机跑要么把 `127.0.0.1 lica_backend` 写进 `/etc/hosts`,要么直接给 `VITE_API_BASE_URL=http://localhost:/api/v1` 绕过 proxy。 注意 dev server 由 `basicSsl` 起 HTTPS —— **必须用 `https://` 访问, 用 `http://` 会直接连不上(curl 报 000)**。跨域需要后端 `CORS_ORIGINS` 加上 `https://localhost:1420`。 ## 测试的坑(重要) - **不要用 `starlette.testclient.TestClient` 测异步 SQLAlchemy 应用。** 它每个请求新建事件循环,而引擎是模块级单例、池里挂着 asyncpg 连接, 跨循环复用会报 `got Future attached to a different loop`,表现为随机 500。 正确做法:`httpx.AsyncClient(transport=httpx.ASGITransport(app=app))` 并在单个 `asyncio.run()` 里跑完全部请求。生产 uvicorn 单循环无此问题。 - 仓库目前**没有** pytest 基建,也没有前端测试脚本。 ## 已知的待修问题(继承自 IRIS 侧 `192c8ee`,本仓库**未修**) - **读接口大面积未鉴权**(已实测,非推测):无 token 直接 200 的包括 `/api/v1/users/`、全部 `/api/v1/dashboard/*`(含 `people-history/export` —— 匿名即可批量导出个人工时台账)、 `/api/v1/analytics/*`、`/api/v1/screen/*`、`/api/v1/orders/`。 写操作和 `/api/v1/tasks`、`/api/v1/products` 是有鉴权的。 新增接口请统一用 `app/core/deps.py` 的 `require_admin` / `require_roles`。 - **对 LICA 的影响面**:`/api/v1/users/` 虽无鉴权,但部门过滤在 SQL 层钉死, 实测无 token 也只能拿到 19 个 LICA 人员,**不会泄露 IRIS 的人**; 其余匿名接口读的是本实例自己的库,跨不到 IRIS 库。真正的风险是 「本部门内部」的越权(如普通操作员能看管理端统计),不是跨部门。 - `AdminLayout` 只判登录不判角色:LICA 的 15 个 `INBOUND` 账号能进 PC 管理端。 - `dashboard_service.py` 查 tasks 时用**小写** `pending/in_progress/completed`, 而 `Task.status` 实际存大写(`PENDING`/`WIP`/`COMPLETED`), 导致管理端「待接收/进行中/已完成」三个数字恒为 0。 (注意 `Product.status` 确实是小写,修的时候别一起改。) - 「管理员角色」这份规则此前散在 4 处(后端 `task_service`、`products.py` 内联、 前端 `constants/task.ts`、`AdminProductsPage` 内联),已因此出过事故。 **后端唯一事实来源是 `app/core/roles.py`,前端用 `constants/task.ts::isAdminRole`。** 新增判断不要手写 `===` 比较。 - `task_logs.task_id` 是 NOT NULL 外键,只能挂任务,不是通用审计。通用审计是 `audit_logs`(本轮新增,由 `app/core/audit_middleware.py` 自动采集)。 ## 约定 - 时间统一北京时间(`app/core/time_utils.py`),库里存 timestamptz。 - 中文枚举标签尽量由服务端下发(如审计接口的 `module_label`/`action_label`), 避免前端再抄一份映射开始漂移。 - 本仓库的提交信息用中文,说明「为什么」而非「改了什么」。