Files
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

662 lines
30 KiB
Markdown
Raw Permalink 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-09-01
当前手机包:**3.1.8+318**`com.fengying.oa.mobile`
本次生产修复与当前真实状态:**先读 `/opt/HANDOFF.md`,再读本文件第 0、2、3、14 节和 `.cursor/rules/`。**
本文件覆盖 **Web + API + 手机 + IM + 库表 + 密钥 + 发版**。不是只写手机端。
**含生产密钥,不要推到公开 git。**
---
## 0. 你接手后立刻要知道的事
1. 这是一家约四十人的工程/软件公司内部 OA,从旧 Java OA(生产 MySQL 库 `oa`)迁到本仓库。
2. 用户同时用 **网页** `https://oa.fysxkj.com`**安卓包**。IM 是自研 TCP,不是环信。
3. 办公模块有固定产品规则(第 3 节)。乱改列表筛选或审批不写下游,会被用户打回。
4. 2026-09-01 的 API 数据修复、旧终止投标恢复兼容和项目类型修复已经发到生产;精确结果与备份位置见 `/opt/HANDOFF.md`。后续不要依据本文件旧版本描述判断是否已部署。
5. 回复用户用**简体中文**。不要主动 `git commit` / `push`,除非用户说。
6. 改菜单后必须跑 `apps/api/prisma/sync-menus.ts`,侧栏才更新。
7. 手机登录/刷新必须带 `clientKind: 'mobile'`
---
## 1. 产品与架构
一套 API,三端消费:
| 端 | 路径 | 技术 | 入口 |
|---|---|---|---|
| API | `apps/api` | NestJS 11 + Prisma 6 + PostgreSQL | `/api/v1`Swagger `/api/v1/docs` |
| Web | `apps/web` | React 18 + Vite 5 + Ant Design 5 + React Query + Zustand | 路由 `apps/web/src/App.tsx` |
| 手机 | `apps/native` | Flutter,包名 **必须** `com.fengying.oa.mobile` | 底栏:消息 / 工作台 / 通讯录 / 我 |
| IM | `apps/im-server/server.js` | Node TCP 8901 + 本机 HTTP 8902 | MobileIMSDK 协议 |
生产:
- 站点:`https://oa.fysxkj.com`
- REST`https://oa.fysxkj.com/api/v1`
- IM TCP`47.96.23.244:8901`
- APK`https://oa.fysxkj.com/fengying-oa.apk`
- 远端代码:`/data/fengying-oa/`
- systemd`fengying-api.service``fengying-im.service`(模板在 `infra/fengying-cloud/`
- 生产 API 端口 **3010**nginx 反代);本机 nest 常在 **3000**
本机构建机 hostname `fykj`,公网不是 `47.96.23.244`
租户字段 `tenantId` 目前固定 `'1'`,没有做多租户产品化。
---
## 2. 仓库地图
```
/opt
├── DELIVERY.md ← 本文件
├── .cursor/rules/office-process.mdc
├── apps/api Nest 后端
│ ├── prisma/schema.prisma 库表
│ ├── prisma/menus.ts 侧栏菜单源
│ ├── prisma/seed.ts 演示数据
│ ├── prisma/sync-menus.ts 菜单写入 Permission
│ ├── src/*.controller.ts 业务入口
│ ├── data/app-release.json 手机 OTA
│ └── .env 本机密钥
├── apps/web/src
│ ├── App.tsx 全部路由
│ ├── pages/ 业务页
│ ├── store/auth.ts 登录态
│ ├── api/client.ts axios + sessionStorage
│ └── labels.ts 枚举中文
├── apps/native Flutter
├── apps/im-server/server.js
└── infra/fengying-cloud nginx / systemd / compose 模板
```
本机常用:
```bash
cd /opt/apps/api && npx nest start # http://localhost:3000/api/v1
cd /opt/apps/web && pnpm dev # http://localhost:5173
cd /opt/apps/im-server && node server.js # TCP 8901
cd /opt/apps/native && flutter build apk --release && bash scripts/publish-apk.sh
```
改 Prisma`cd /opt/apps/api && npx prisma migrate dev`
改菜单:改 `prisma/menus.ts``npx ts-node --transpile-only prisma/sync-menus.ts`
---
## 3. 硬约束(违反就是返工)
### 3.1 个人办公怎么长
规则文件:`.cursor/rules/office-process.mdc`
- 列表筛选用 **`ApplyBucketBar`**Segmented + `?bucket=`),有选中态。
- 标准桶:全部 / 待审批 / 已通过 / 已驳回。领域附加桶可以加,不能替代标准桶。
- 禁止页头三个无选中态的 `Link`(「全部」「待审批」「外带未还」这种)。
- 「我的申请」=`/office/flow` 是**跟踪**;人事/用章/办公申请是**发起**;「待我审批」是**审批**。不要做成三套互不相通的列表。
- 申请人页只看自己的单;台账在业务模块(用章登记、资产管理、采购申请)。
审批通过必须写下游:
| 申请 | 落到 |
|---|---|
| 资产购置 `ASSET_BUY` | 采购单 + 待入库台账 + 付款待办 |
| 资产领用 / 用车 | 占用记录(挂台账 id),**交回不改保管人** |
| 出差(人事) | 差旅报销草稿 |
| 用章外带 | 归还待办 |
保管人 = 台账责任人。占用 = 谁正在用。占用时不要把保管人改成持有人。
实现:`apps/api/src/office/office-apply.ts`
### 3.2 登录会话
`apps/api/src/auth/auth.service.ts` + Web `apps/web/src/api/client.ts`
- Web 和手机**可以同时在线**。
- 同一账号不能开两个 Web(后登挤掉先登)。JWT 含 `sid`
- 手机 `clientKind: 'mobile'`refresh **不轮换**access 7 天,refresh 3650 天,除非退出一直在线。
- Web token 用 **sessionStorage** + BroadcastChannel(同浏览器多标签共享)。关浏览器再开要重登。
- 桌面端只能扫码,**不能账密**。`clientKind: 'desktop'` 账密会被拒绝。页 `/login/desktop`;手机「我 → 确认电脑登录」;Deep Link `fengyingoa://qr`
- 登出不带 refresh 时只撤 web/desktop,不踢手机。
### 3.3 安卓
- `applicationId``com.fengying.oa.mobile``namespace` 仍是 `com.fysxkj.fengying_oa`,不要改混。
- 当前 release 用 **debug 签名**。华为控制台正式 SHA256 对不上 debugHMS token 可能拿不到。
- **不要**加 `com.huawei.agconnect` Gradle 插件(AGP 8.6 会炸)。HMS 依赖 + `agconnect-services.json` + Manifest `appid` 留着即可。
### 3.4 数据范围
`AuthUser.dataScope``ALL` / `DEPT` / `SELF`。列表过滤在 `apps/api/src/common/data-scope.ts`。改列表不要只改前端。
---
## 4. 角色、权限、菜单
菜单源:`apps/api/prisma/menus.ts`,写入 `Permission``permType=MENU`)。
`admin` / `owner` 前端 `canSee` 直接放行全部。其他人比 `permissions` 前缀。
种子角色(`prisma/seed.ts`,生产还会有导入岗位):
| code | 名称 | dataScope | 大致权限 |
|---|---|---|---|
| admin | 系统管理员 | ALL | * |
| owner | 企业负责人 | ALL | * |
| biz_director | 商务总监 | DEPT | office/party/seal/bid/contract/finance/report |
| biz_staff | 商务专员 | SELF | office/party/seal/bid/contract |
| tech_director | 技术总监 | DEPT | office/bid/project/report |
| rd_director / 3d_director / video_director / material_director | 各技术条线总监 | DEPT | 见 `role-groups.ts` |
| finance | 财务 | ALL | office/finance/contract/report |
| hr | 人事 | ALL | office/hr + 人事设置 |
| employee / pm / 各 *\_staff | 员工/项目经理 | SELF | office + 工时等 |
| seal / asset / cashier … | 导入岗位 | 视脚本 | `fix-oa-posts.ts` |
总监集合:`DIRECTOR_ROLES`。可发短信:`SMS_SEND_ROLES`。可派工作:`WORK_ASSIGN_ROLES`
`bid-workflow.ts` 仍保留旧的 `SPECIAL_COORDINATORS` 常量用于兼容,但当前特殊筛选/特殊制标的办理人加载逻辑是全体在职人员,禁止再改回写死两人。
首次登录非 admin:改密 + 完善档案(`/office/onboard`)。`mustChangePassword` / `profileCompletedAt`
Web 侧栏:`AppLayout``/auth/me` 带回的 `menus`
发公告:`hr` 与各总监(`NOTICE_PUBLISH_ROLES` in `App.tsx`)。
---
## 5. Web 功能全表(按侧栏)
路由全部在 `apps/web/src/App.tsx`。没有单独的移动端 Web。
### 5.1 工作台 `/workbench`
`OverviewPage`:待办/待审批数字、天气问候、快捷入口。API:`GET /office/overview``GET /office/weather`
### 5.2 个人办公
| 菜单 | 路径 | 页面 | API 要点 | 说明 |
|---|---|---|---|---|
| 待办事项 | `/office/todos` | OfficeTodosPage | `GET /office/todos` `PATCH /office/todos/:id` | 待办可标已办 |
| 待我审批 | `/office/approvals` | OfficeApprovalsPage | `GET /office/approvals` `POST .../decide` | 统一审批入口 |
| 我的申请 | `/office/flow` | MyFlowPage | `GET /office/flow` | 只跟踪自己的单 |
| 我的日程 | `/office/calendar` | CalendarPage | `GET /office/calendar` | 含证照/印章逾期、开标等 |
| 工作汇报 | `/office/reports` | WorkReportPage | `/office/reports*` | 可从工时草稿带入 |
| 我的费用 | `/office/expenses` | MyExpensesPage + Expense/Loan 详情 | `/office/my-expenses` `/expenses` `/loans` | 员工发起报销/借款 |
| 人事申请 | `/office/hr-apply` | HrApplyPage / HrLeaveDetailPage | `/leave-requests` | 请假加班出差外出离职晋升 |
| 用章申请 | `/office/seal-apply` | SealRequestListPage mode=apply | `/seal-requests` | 发起;台账在证照用章 |
| 办公申请 | `/office/apply` | OfficeApplyPage | `/office/applies` | 购置/领用/用车 |
| 公司公告 | `/office/notices` | NoticePage / NoticeDetailPage | `/office/notices` | 员工阅读 |
| 发送短信 | `/office/sms` | OfficeSmsPage | `/sms/*` | 总监/人事/admin |
| 工作安排 | `/office/assign` | WorkAssignPage | `/office/work-assignments` | 总监派任务 |
| 消息 | `/office/im` | ImPage | `/im/*` | Web IM + 通话弹层 |
| 员工通讯 | `/office/directory` | DirectoryPage | `/staff` `/directors` | 点人可开聊 |
出差通过后应落差旅报销草稿(人事申请下游)。
### 5.3 往来单位
| 菜单 | 路径 | 页面 | API |
|---|---|---|---|
| 招标单位 | `/party/tenders` | TenderPartyPage | `/parties` |
| 参与主体 | `/party/entities` | LegalEntityPage | `/legal-entities` |
| 联系名录 | `/party/contacts` | ContactDirectoryPage | `/contacts` |
本机已洗过 5 家主体:随行科技(默认,历史投标多挂这里)、风影软件、风影工程、山东鲁兵、隆创。脚本 `prisma/wash-legal-entities.ts`。OA dump 几乎没有「软件/工程」历史投标,不会自动拆单。
### 5.4 证照用章
| 菜单 | 路径 | 页面 | API |
|---|---|---|---|
| 资质台账 | `/seal/qualifications` | QualificationListPage | `/qualifications` |
| 证照借用 | `/seal/borrows` | BorrowListPage | `/credential-borrows` |
| 用章登记 | `/seal/requests` | SealRequestListPage mode=register | `/seal-requests` |
| 印章台账 | `/seal/registry` | SealRegistryPage | `/seals` |
印章状态:`IN_STOCK` 在库,`OUT` 外带(Web 台账有特判)。外带审批通过写归还待办。
### 5.5 投标管理(核心)
状态机:`apps/api/src/bidding/bid-workflow.ts`
列表桶:`BidListPage` + `?bucket=`
```
普通 FLOW
项目筛选 TECH_INITIAL → FETCH → TECH_FINAL
→ 标书制作 DRAFTING → REVIEW1 → REVIEW2 → PRINT
→ 投标执行 LIST → PENDING
→ 开标结果 WON / LOST / FAILED
随时可 TERMINATED(权限内)
特殊 SPECIAL:特殊筛选 / 特殊制标的办理人从**全公司在职人员**中选择,不再写死刘运泽、陈铖。
```
| 菜单 | 路径 | bucket |
|---|---|---|
| 项目筛选 | `/bid/screening` | screening(可新建) |
| 特殊筛选 | `/bid/special-screening` | 特殊 |
| 标书制作 | `/bid/drafting` | drafting |
| 特殊制标 | `/bid/special-drafting` | drafting+special |
| 投标执行 | `/bid/execution` | execution |
| 开标结果 | `/bid/result` | result |
| 终止投标 | `/bid/terminated` | terminated |
详情 `/bid/cases/:id``BidDetailPage`。新建:`BidCreatePage`
中标:`ensureWonLedger` 立刻落 **合同草稿 + 项目台账**(幂等)。
未中/终止:可「编辑」登记对方中标单位/金额 → 报表「落标对照」。
API`/bid-cases``PATCH /bid-cases/:id/competitor`
### 5.6 合同管理
中标自动生成 `kind=TENDER` 草稿。直签/其他在本页新建,不互相重复。
| 菜单 | 路径 | 说明 |
|---|---|---|
| 投标合同 | `/contract/tender` | 中标来的 |
| 直签合同 | `/contract/direct` | 不经投标 |
| 其他合同 | `/contract/incremental` | 其余 |
| 人事合同 | `/contract/hr` | `LaborContractPage` `/labor-contracts` |
详情 `/contract/:id`。API `/contracts`
### 5.7 资产管理
数字资产 `/asset/digital`、固定资产 `/asset/fixed``AssetRegistryPage``/registered-assets`
状态含 `IN_STOCK` / `PENDING_IN` / 占用。入库:`POST /registered-assets/:id/inbound`
领用占用走办公申请,不在本页把保管人改掉。
### 5.8 项目管理
中标自动写入台账。直签可在台账补登。
| 菜单 | 路径 | API |
|---|---|---|
| 项目台账 | `/project/list` | `/projects` |
| 任务分解 | `/project/tasks` | `/project-tasks` |
| 工时填报 | `/project/timesheets` | `/timesheets` |
| 资源看板 | `/project/resources` | OpsPages ResourceBoardPage |
| 项目验收 | `/project/acceptance` | 同一 ProjectListPage mode=acceptance |
| 采购申请 | `/project/purchases` | PurchasePage(购置审批下游) |
| 器材占用 | `/project/assets` | AssetPage 占用记录 |
详情 `/project/:id`。预算/报销:商务差旅进商务,技术部门差旅/工时/采购进技术(页面 description 已写)。
### 5.9 财务收支
员工在「我的费用」发起;财务在本模块确认、付款、开票。草稿财务看不到(`canSeeSubmittedFinance`)。
| 菜单 | 路径 | API |
|---|---|---|
| 费用报销 | `/finance/expenses` | `/expenses` |
| 借支记录 | `/finance/loans` | `/loans` |
| 投标/履约/质保金 | `/finance/bonds/*` | `/bonds` type=TENDER/PERFORMANCE/WARRANTY |
| 开票收款 | `/finance/invoices` | `/invoices` |
| 付款执行 | `/finance/payments` | `/payments` |
报销必须挂费用对象:投标 / 正式项目 / 部门 / 行政,规则 `assertCostObject`。中标前挂投标,中标后挂项目,不要同时挂。
### 5.10 组织人事
| 菜单 | 路径 | 说明 |
|---|---|---|
| 在职员工 | `/hr/employees` | 档案、证件、银行卡 |
| 组织管理 | `/hr/org` | 部门树 |
| 入转调离 | `/hr/lifecycle` | EmploymentEvent |
| 假勤结果 | `/hr/attendance` | 月导入 + 打卡 |
| 薪酬核算 | `/hr/payroll` | 核算批次 |
| 绩效发放 | `/hr/performance` | |
| 发布公告 | `/hr/notices` | 人事发 |
| 规章制度 | `/hr/policies` | 同类 Noticekind 不同 |
### 5.11 经营报表
| 菜单 | 路径 | API |
|---|---|---|
| 投标转化 | `/report/conversion` | `/reports/bid-conversion` |
| 落标对照 | `/report/lost` | `/reports/competitor-wins` |
| 项目利润 | `/report/profit` | `/reports/profit` |
| 部门人效 | `/report/efficiency` | `/reports/efficiency` |
| 资金占用 | `/report/cash` | `/reports/cash` |
页面在 `OpsPages.tsx`
### 5.12 系统设置
| 菜单 | 路径 | 说明 |
|---|---|---|
| 员工账号 | `/system/acl` | 用户/角色 |
| 人事参数 | `/system/hr-params` | |
| 考勤设置 | `/system/attendance-rules` | |
| 数据字典 | `/system/dicts` | |
| 权限设置 | `/system/perms` | 菜单授权 |
| 编号规则 | `/system/numbers` | `nextBizNo` |
| 短信服务 | `/system/sms` | 签名/模板/绑定事件 |
| 消息通道 | `/system/messages` | 占位页,文案可能过时(IM 已做) |
| 手机端升级 | `/system/app-release` | 写 `app-release.json` |
| 操作日志 | `/system/logs` | |
公开:`GET /system/app-release`(手机 OTA)。
---
## 6. 核心流程(改业务先对这条链)
### 投标 → 合同/项目
筛选 → 取标 → 终审 → 制标 → 一审二审 → 印封 → 执行 → 开标。
**WON** → 合同草稿 + 项目台账。售前报销仍挂投标;正式花费挂项目。
### 报销 / 借款
员工草稿 → 提交 PENDING → 财务/总监审批 APPROVED → 付款执行。借款还有 OPEN / OFFSET 冲账。
### 办公申请
发起 PENDING → 审批(admin/owner/hr/biz_director)→ 按 kind 写采购/占用/待入库。
### 用章
申请 → 审批 → 外带则归还待办;台账 status 在库/外带。
### 人事假勤
`LeaveRequest.kind`LEAVE / OVERTIME / TRIP / OUT / RESIGN / PROMOTE。出差通过 → 报销草稿。
统一待办 `TodoTask`、统一审批 `ApprovalTask``bizType` 区分投标/合同/报销/用章/人事/办公)。
待我审批一页消化,不要为每个 biz 再做孤立审批页。
---
## 7. API 地图
前缀均 `/api/v1`。鉴权 JWT,除标注 Public。
| 模块 | 文件 | 主要路径 |
|---|---|---|
| 登录 | `auth/` | `/auth/login` `/auth/refresh` `/auth/me` `/auth/logout` `/auth/profile` `/auth/password` `/auth/qr/*` |
| 办公 | `office/` | `/office/*` 待办审批日程公告申请汇报工作安排天气地理 |
| 投标 | `bidding/` | `/bid-cases` |
| 合同 | `contract/` | `/contracts` |
| 项目 | `project/` | `/projects` `/project-tasks` `/timesheets` |
| 用章 | `seal/` | `/seal-requests` `/seals` `/qualifications` `/credential-borrows` |
| 财务 | `finance/` | `/expenses` `/loans` `/bonds` `/invoices` `/payments` |
| 人事 | `hr/` | `/employees` `/leave-requests` `/payroll` `/attendance` `/labor-contracts` `/org` |
| 主数据 | `master/` | `/parties` `/legal-entities` `/contacts` `/registered-assets` |
| 系统 | `system/` | `/system/menus` `/system/acl` `/system/dicts` `/system/numbers` `/system/app-release` `/system/logs` |
| 报表 | `report/` | `/reports/*` |
| 文件 | `files/` | `POST /files` `GET /files/:id` |
| IM | `im/` | `/im/conversations` `/im/messages` `PATCH /im/messages/:id` `/im/calls*` `/im/read` |
| 推送 | `push/` | 设备 token 注册与下发 |
| 短信 | `sms/` | 签名模板发送 OTP |
| 通讯录 | `business/` | `/staff` `/directors` |
| 健康 | `health/` | `/health` |
| 协议 | `legal/` | 用户协议页 |
响应外包一层 `{ data }`Web 用 `unwrapList` / `body.data`。手机 `oa_client.dart` 同样解包。
错误:`HttpErrorFilter`。请求号:`X-Request-Id`
地理:`/office/geo/*` 走腾讯位置服务,`TENCENT_MAP_KEY`
---
## 8. 数据模型(Prisma
完整定义:`apps/api/prisma/schema.prisma`
**账号**User、RefreshToken`clientKind`)、AuthQrTicket、Role、Permission、UserRole、PushDevice
**组织人事**Department、Employee、LaborContract、EmploymentEvent、AttendanceMonth、AttendancePunch、LeaveRequest、HrSetting、AttendanceRule、PayrollRun、PayrollItem
**主数据**LegalEntity、BusinessParty、PartyContact、DictItem、NumberRule、Qualification、SealItem、RegisteredAsset
**投标**BidCase(含 `winnerName/Amount/Note`)、BidReviewer、BidAssignee、BidDocVersion、BidActionLog
**合同项目**Contract、ContractReviewer、PaymentMilestone、Project、ProjectBudget、ProjectTask、Timesheet、ProjectAcceptance、ProjectAcceptMaterial、ProjectChange
**财务**ExpenseClaim、ExpenseLine、LoanRecord、BondRecord、InvoiceRecord、PaymentRecord、PurchaseRequest
**办公**TodoTask、ApprovalTask、WorkReport、WorkAssignment、OfficeApply、Notice、AssetOccupancy、CredentialBorrow、SealRequest
**IM**ImConversation、ImConversationMember、ImMessage`fingerprint` 唯一)、ImReadCursor、ImMessageReceipt、ImCall、ImCallMember、ImCallSignal
**其它**FileAsset、SmsSendLog、SmsEventBind、SmsOtp、OperationLog
编号:`NumberRule` + `src/system/numbering.ts`。注意:**项目筛选、特殊筛选的招标编号和合同编号必须手填,系统不得另生成隐藏编号**;报销等内部单据仍可使用系统编号。
---
## 9. 手机端
Flutter。版本:`apps/native/lib/app_config.dart` + `pubspec.yaml` + `test/widget_test.dart` + `apps/api/data/app-release.json`,四者一起改。
默认连生产 API / IM。底栏四页:`messages_page``workbench_page``directory_page``mine_page`
工作台宫格来自登录菜单。`open_module.dart` / `module_list_page.dart` 把菜单码映射到 REST 列表(投标→`/bid-cases` 等)。系统设置、发短信、工作台里的「消息」入口故意不打开(消息在底栏)。详情 `record_detail_page.dart` + `biz_route.dart` 反查详情 API。中文:`labels.dart`
聊天/通话的关键文件:聊天 `chat_page.dart`,来电 `home_shell.dart` + `call_page.dart`。当前版本为 3.1.8+318;音视频、截屏提醒、撤回再编辑、会话已读后保留等需求必须按 `/opt/HANDOFF.md` 的“未完成/需真机回归”继续验证,不能仅凭旧交接宣称已修。
打包:
```bash
cd /opt/apps/native && flutter build apk --release && bash scripts/publish-apk.sh
```
产物:`/opt/apps/native/dist/fengying-oa.apk`。本机 root 跑 Flutter 会警告,仍能编过。下一版必须在 **3.1.8+318** 基础上递增。
OTA:比较 `build`。后台「手机端升级」。
---
## 10. IM
协议:4 字节大端长度 + UTF-8 JSON Protocal。登录用 OA accessToken 调 `GET /auth/me`
**新 `server.js`**`Map<userId, Set<socket>>`,同一账号多端/重连 **不再 kick**。旧版会踢旧连接,客户端若把 `_want=false` 会永久断线。
生产必须换新 `server.js` 长连接才稳。新 APK 即使对旧 IM 也不会再「被踢后永不再连」。
HTTP 消息仍落 Postgres。TCP 负责推送。通话信令走 `/im/calls/:id/signal`HTTP 轮询),WebRTC + 国内 STUN。
`PATCH /im/messages/:id`2 分钟内改自己的文本。
---
## 11. 文件、短信、推送、地图
- 文件:`FilesService` 存 MinIO(本机 docker)或本地 `uploads/`。下载带鉴权。手机 `AuthImage` / `fileBytes` 有内存缓存。
- 短信:阿里云 Dysmsapi。系统设置申请签名/模板,事件绑定在 `SmsEventBind`。投标节点会 `emitSms`
- 推送:vivo / OPPO / 魅族 / 华为。设备 `PushDevice`。华为 App ID `118301849`
- 地图:腾讯。选点 `location_pick_page.dart` + `tencent-lbs`;POI 用真实距离,不要写死「100m内」。
---
## 12. 前端约定
**Web**
- 新列表:Ant Design Table + 中文 `zh()` / `StatusTag`,不要直接渲染枚举码。
- 申请类列表:`ApplyBucketBar` + URL `bucket`
- 抽屉表单:`FormDrawer.tsx`。附件:`FileAttachments.tsx`
- 枚举中文:`apps/web/src/labels.ts`,与手机 `labels.dart` 对齐。
- token 只放 sessionStorage。
**手机**
- 企业微信风:`theme.dart``widgets/wecom.dart`
- 英文字段:补 `fieldZh` / `zhMap`,不要在单个页写死。`OUT` 人事=外出、印章=外带,有冲突时按字段上下文。
---
## 13. 脚本与历史数据
`apps/api/prisma/` 里常见脚本(跑前看文件头,生产库只读):
| 脚本 | 用途 |
|---|---|
| seed.ts | 空库演示 |
| sync-menus.ts | 菜单/权限 |
| import-hr.ts / import-prod.ts | 从旧 OA 导人事/业务 |
| wash-legal-entities.ts | 参与主体清洗 |
| wash-oa-ledger.ts / wash-oa-flow-finance.ts | 台账/财务清洗 |
| sync-oa-attachments.ts | 附件 |
| grant-admin-equivalents.ts / ensure-prod-admin.ts | 管理员 |
| fix-oa-posts.ts / fix-expense-applicants.ts | 岗位与报销申请人 |
旧 OA 在生产 MySQL `oa`(只读扒数,用户禁止改生产旧库、禁止整用户过来)。dump 曾放 `/opt/import/prod/`
---
## 14. 密钥与账号(Codex 直接用,不必再问)
### 生产 SSH
```
主机:47.96.23.244
用户:root
密码:VYTnNdfFfrX7xTP
代码目录:/data/fengying-oa/
```
```bash
export SSHPASS='VYTnNdfFfrX7xTP'
RSH='ssh -o StrictHostKeyChecking=no'
DEST=root@47.96.23.244
sshpass -e ssh -o StrictHostKeyChecking=no "$DEST" 'hostname; systemctl is-active fengying-api fengying-im'
```
发版:
```bash
export SSHPASS='VYTnNdfFfrX7xTP'
RSH='ssh -o StrictHostKeyChecking=no'
DEST=root@47.96.23.244
# 先 nest build、确认 APK 已 publish
sshpass -e rsync -az -e "$RSH" /opt/apps/api/dist/ $DEST:/data/fengying-oa/apps/api/dist/
sshpass -e rsync -az -e "$RSH" /opt/apps/api/data/app-release.json $DEST:/data/fengying-oa/apps/api/data/app-release.json
sshpass -e rsync -az -e "$RSH" /opt/apps/web/dist/ $DEST:/data/fengying-oa/apps/web/dist/ # 若本机有最新 web 构建
sshpass -e rsync -az -e "$RSH" /opt/apps/im-server/server.js $DEST:/data/fengying-oa/apps/im-server/server.js
sshpass -e rsync -az -e "$RSH" /opt/apps/native/dist/fengying-oa.apk $DEST:/data/fengying-oa/apps/web/dist/fengying-oa.apk
sshpass -e ssh -o StrictHostKeyChecking=no "$DEST" 'systemctl restart fengying-api fengying-im; sleep 2; systemctl is-active fengying-api fengying-im'
```
生产 API `.env` 在远端 `/data/fengying-oa/apps/api/.env`。上去先 `cat`**不要用本机 development 的 JWT/Postgres 覆盖生产**。发 IM/API 后确认 Prisma 已有 `RefreshToken.clientKind``AuthQrTicket``BidCase.winner*`
### 旧 OA MySQL(只读)
```
用户:root
密码:BtH9ywG8gFTXG9w!
库:oa
```
### 本机 API `apps/api/.env`
```
NODE_ENV=development
APP_PORT=3000
DATABASE_URL=postgresql://ops:ops_dev_only@localhost:5432/enterprise_ops
REDIS_URL=redis://localhost:6379
MINIO_ACCESS_KEY=opsadmin
MINIO_SECRET_KEY=opsadmin_dev_only
JWT_ACCESS_SECRET=change-me-access-secret-dev-only
JWT_REFRESH_SECRET=change-me-refresh-secret-dev-only
POSTGRES_USER=ops
POSTGRES_PASSWORD=ops_dev_only
POSTGRES_DB=enterprise_ops
ALIYUN_SMS_ACCESS_KEY_ID=LTAI5t9DdPycJp7sabQyEWRc
ALIYUN_SMS_ACCESS_KEY_SECRET=RPFVqKDUpEvxo3IsqlxLB5qIywfheM
ALIYUN_SMS_ENDPOINT=dysmsapi.aliyuncs.com
ALIYUN_SMS_REGION=cn-hangzhou
ALIYUN_SMS_DEFAULT_SIGN=江苏风影随行科技
ALIYUN_RAM_LOGIN=yongkangdai10@1801267455383083.onaliyun.com
TENCENT_MAP_KEY=L6NBZ-47KLI-OL7G2-UMROZ-XPI3T-SLFC7
IM_TCP_PORT=8901
IM_HTTP_PORT=8902
IM_INTERNAL_TOKEN=fengying-im-internal
VIVO_PUSH_APP_ID=106114371
VIVO_PUSH_APP_KEY=b521c805229a00e85a53d4b535a0cafe
VIVO_PUSH_APP_SECRET=2e1536ed-ed2f-4cd6-88d1-67aa9a93eeba
OPPO_PUSH_APP_ID=37438377
OPPO_PUSH_APP_KEY=e23832b52fbb46e18de6eef5affda2e9
OPPO_PUSH_APP_SECRET=e9cf0bf35052484ebcaf15d56576468b
OPPO_PUSH_MASTER_SECRET=e9cf0bf35052484ebcaf15d56576468b
MEIZU_PUSH_APP_ID=157643
MEIZU_PUSH_APP_KEY=4263a8d18de442b6a17203866ce1546d
MEIZU_PUSH_APP_SECRET=2a0bafc2d47a470f8b2aad3f182d2377
HUAWEI_PUSH_APP_ID=118301849
HUAWEI_PUSH_CLIENT_ID=118301849
HUAWEI_PUSH_CLIENT_SECRET=85c01c5c853d571e2d83fd8629efb21e9b2cad0bdfadd6f32c2c5b42e38d7888
```
本机 IM `.env` 写的是 `OA_API_URL=http://127.0.0.1:3010/api/v1`,本机 nest 常在 3000,连本机 IM 请改 3000。生产 systemd`OA_API_URL=http://127.0.0.1:3010/api/v1`
### 华为 AGC
- 包名 `com.fengying.oa.mobile`App ID `118301849`
- 服务端 Client Secret`85c01c5c853d571e2d83fd8629efb21e9b2cad0bdfadd6f32c2c5b42e38d7888`
- JSON`apps/native/android/app/agconnect-services.json`
- JSON `client.client_id``1993069421077225280`
- JSON `client.client_secret``93585E101B911EFF0F99F215050CD3E4297F67CB004E0D8E070AAB1EEDD7E725`
- JSON `client.api_key``DgEDAFLpMeUxEsIEiKams+kYWThvAg0ioMuL1rW9BEP4i71BuChEPD7Udg1AbDKbg+ljwD+pBACxktLqHwmUlSxOCwsLHiRAKQDBFw==`
- `cp_id``10086000847744494``product_id``101653523864520415`
### OPPO Maven
```
https://maven.columbus.heytapmobi.com/repository/releases/
username: nexus
password: c0b08da17e3ec36c3870fed674a0bcb36abc2e23
```
### 腾讯地图 / vivo Manifest
- `TENCENT_MAP_KEY=L6NBZ-47KLI-OL7G2-UMROZ-XPI3T-SLFC7`
- Manifest vivo`api_key=b521c805229a00e85a53d4b535a0cafe``app_id=106114371`
### 本机演示账号
`admin` / `Admin@123456`。种子其他人常见 `Demo@123456` 或导入后 `123456`。不要拿去撞生产。
---
## 15. 发生产清单
1. `npx nest build`,同步 `apps/api/dist/`
2. 若改了 Web`pnpm --filter web build`,同步 `apps/web/dist/`
3. 同步 `server.js`、APK、`app-release.json`
4. `systemctl restart fengying-api fengying-im`
5. 后台确认手机端升级为当前最新 build(现为 318)
6. 真机覆盖安装当前最新版(现为 3.1.8)
**APK 保留规则:**云主机只保留当前 OTA 指向的最新安装包(对外统一为 `apps/web/dist/fengying-oa.apk`)。每次发版先精确列出 `/data/fengying-oa` 下的 `.apk` / `.aab`,确认新包可下载后删除其他旧包,不要在 `apps/native/dist` 等目录留历史副本。
API/IM/APK 是否为同一版必须分别核对,不能只看网页或 OTA JSON。
---
## 16. 已知坑 / 未完成
1. 生产 API 已同步 2026-09-01 的数据修复代码;Web、IM、APK 后续每次仍要分别核对版本和构建时间。
2. 华为正式推送:缺正式签名 + AGConnect 插件未进构建。
3. `OUT` 中文:人事外出 vs 印章外带。
4. 系统设置「消息通道」文案还写「本期不做原生 IM」,已过时。
5. Web IM 与手机 IM 都可用,但通话依赖双方打开页 + STUN,失败别先改来电弹层。
6. 手机工作台对报表/部分系统页映射为 `null`,点了可能没页。
7. 打开 docx 依赖手机已装 WPS/Office。
8. 历史投标主体不会自动拆到软件/工程公司。
9. `apps/native/README.md`、部分 api README 版本号过时,以 `app_config.dart` 和本文件为准。
10. 薪酬/绩效/资源看板有页面,深度以现网数据为准,缺的是补流程不是再铺空壳。
11. 问题视频:`/opt/问题视频/`。来电测试对象常为「戴永康」。
---
## 17. 建议接着做的顺序
1. 用第 14 节发生产(API + Web + IM + APK + OTA),不要再问密码。
2. 真机覆盖 2.3.2:来电能关、消息上屏、图片/地图能点、印章「在库」。
3. 网页重新登录:确认扫码登电脑、Web 互踢、侧栏「落标对照」。
4. 再改业务:遵守第 3 节办公规则;改投标走 `bid-workflow.ts`,不要只改一个列表页。
5. 英文残留:补 `labels.ts` / `labels.dart`
6. 华为推送真机(正式签名)。
7. 用户新需求再开功能,不要平行重构目录。
改完菜单同步 `sync-menus.ts`。改完枚举两边 labels 一起改。改完版本四个文件一起改。