Files
track-LICA/AGENTS.md
duxingchen 347b7b6a68 feat: LICA 部门独立实例 — 组织隔离与端口/标识改造
派生自 IRIS 实例的 feature/ai-audit-update @ 192c8ee,在同一台机器上独立运行。

隔离机制(开关集中在 app/core/config.py 的 ORG_DEPARTMENT / MATERIAL_CATEGORY_PREFIX):
- 登录:sys_user 查询增加 department 条件,非本部门账号一律 401
- 人员列表:服务端钉死部门、忽略客户端传参;删除「异常退回全表」的降级分支
- 物料:groups 与 items 都增加 category LIKE 'LICA/%' 前缀过滤
- 人员操作统计:把硬编码的 department='IRIS' 改为配置项

物料为什么用前缀而不是 LIKE '%LICA%':
MOM 里存在 171 条 IRIS/成品/LICA/...(无人机/野外便携/高塔监测等),
模糊匹配会把这些 IRIS 物料漏给 LICA。实测前缀匹配命中 795 条 / 5 个分组。

部署隔离:
- 端口 8030/8031/8032,容器名 lica_*,卷 lica_pgdata(与 IRIS 完全独立)
- 服务名改为 lica_backend,避免在 projects_default 网络上与 IRIS 的 backend
  重名 —— 否则将来任何一方写 http://backend:8000 会随机打到另一个部门
- SECRET_KEY 重新生成:实测两边 token 互不通用(双向 401)

客户端标识(不改会导致两个部门的客户端互相覆盖):
- Tauri identifier 改 com.lica.production(否则桌面端互相覆盖安装,且共用
  WebView 数据目录会让 track_admin_token 串号)
- uni-app appid 改 __UNI__D2F4A19(否则同机 APK 互相覆盖、wgt 热更新串号)
- uni-app 地址端口 8011 → 8031(收敛在 utils/config.js 单一来源)
- sync-watch.sh 的 DST 指向 LICA 专属 HBuilderX 目录(否则会把源码灌进 IRIS 工程)

排除项:未复制 deploy.sh / deploy_full.sh / docker-compose.prod.yml ——
它们写死了 IRIS 的生产服务器,误跑会覆盖线上系统。
2026-09-21 16:10:52 +08:00

114 lines
7.2 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.

# 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) + AlembicPostgreSQL。
- `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:<port>/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`
避免前端再抄一份映射开始漂移。
- 本仓库的提交信息用中文,说明「为什么」而非「改了什么」。