Files
2026-09-16 22:16:17 +08:00

361 lines
20 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.
# Trade Message Center
## 本地开发要求
- Node.js `>=22.22.2 <23`
- pnpm `11.7.0`
- 可连接的 PostgreSQL 数据库
- Chrome 或 Chromium(用于加载和调试扩展)
安装依赖:
```bash
pnpm install
```
## GitNexus 代码智能
GitNexus 会把代码解析为符号、调用关系和执行流程图,适合在改动前定位实现、评估影响范围,以及在提交前核对实际波及的模块。索引保存在本机的 `.gitnexus/`,不会提交到 Git。
### 安装与首次建索引
本仓库已经生成过 `.gitnexus/run.cjs`;日常使用不需要全局安装。新 clone 没有该文件时,在仓库根目录执行一次:
```bash
npx gitnexus@latest analyze
```
若本机的 npm 11 在安装原生依赖时失败,改用 pnpm:
```bash
pnpm --allow-build=@ladybugdb/core --allow-build=gitnexus --allow-build=tree-sitter \
dlx gitnexus@latest analyze
```
也可以安装全局命令,之后上述本地 runner 会优先使用它:
```bash
npm install -g gitnexus
```
### 索引维护
在进行较大代码变更后、GitNexus 报告索引过期时,或需要重新生成 AI 指引文件时,执行:
```bash
node .gitnexus/run.cjs analyze
```
常用维护命令:
```bash
node .gitnexus/run.cjs status # 查看索引状态、版本和统计信息
node .gitnexus/run.cjs list # 列出本机已索引仓库
node .gitnexus/run.cjs clean # 删除当前仓库的本地索引;执行前会要求确认
```
`clean` 会移除 `.gitnexus/`,只在索引损坏或确定不再需要时使用;随后可用 `analyze` 重新生成。
### 在 AI 编程会话中使用
已配置 GitNexus MCP 的客户端(如本项目的 Codex/Claude 环境)可直接读取 `gitnexus://repo/trade-message-center/context` 确认索引是否可用。推荐按任务类型选择工具:
| 目标 | 工具 | 示例 |
| -------------------------- | ---------------- | --------------------------------------------------------------- |
| 理解功能或调用链 | `query` | `query({ query: "OneTalk 授权" })` |
| 查看某个符号的完整上下文 | `context` | `context({ name: "authorizeBinding" })` |
| 改动前评估调用方与风险 | `impact` | `impact({ target: "authorizeBinding", direction: "upstream" })` |
| 提交前确认当前 diff 的影响 | `detect_changes` | `detect_changes({ scope: "compare", base_ref: "main" })` |
| 跨文件重命名 | `rename` | 使用工具执行,不要文本替换 |
工作约定是:探索陌生逻辑时先用 `query`,修改函数、类或方法前先执行 `impact`;若结果为 HIGH 或 CRITICAL,先确认影响范围再继续。提交前执行 `detect_changes`,核对变更只覆盖预期的符号和执行流程。
## 环境变量与授权配置
环境变量文件统一放在仓库根目录,供 server、Chrome 扩展和数据库迁移共享:
- `.env.example`:提交到仓库的变量模板,不放真实密钥。
- `.env``.env.development`:可选的基础或开发环境配置,不提交。
- `.env.local`:本机通用配置,不提交。
文件名统一使用 `.env.example``.env.development`;不要使用 `.env.Example``.env.dev`
开发环境可以从模板开始:
```bash
cp .env.example .env.local
```
server 开发入口和迁移命令按以下顺序读取文件,后面的文件覆盖前面的同名变量:
`.env``.env.local``.env.development``.env.development.local`
### 必填基础配置
| 变量 | 说明 |
| -------------------------------------------- | -------------------------------------------------------------------- |
| `HOST` | server 监听地址,例如 `127.0.0.1`。 |
| `PORT` | server 监听端口,必须是 `1-65535` 的整数,例如 `7878`。 |
| `DATABASE_URL` | Bright PostgreSQL 连接串;启动 server 和执行 migration 都需要。 |
| `OSS_BUCKET``OSS_ENDPOINT` | Chrome 扩展发布包及本地联调上传所用的 OSS Bucket 与 HTTPS Endpoint。 |
| `OSS_ACCESS_KEY_ID``OSS_ACCESS_KEY_SECRET` | 仅服务端使用的 OSS V1 下载签名与联调页 server-proxy PUT 凭据。 |
本地默认配置如下:
```dotenv
HOST=127.0.0.1
PORT=7878
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/trade_message_center
```
### OneTalk 本地开发
`pnpm dev` 只以 `NODE_ENV=development` 启动 Bright server 与 Chrome 扩展。`mind-test-harness` 仅允许在 `NODE_ENV=development` 下运行,是顺道使用的低优先级手工工具;必须另开终端执行 `NODE_ENV=development pnpm dev:harness`,不得在 test、staging 或 production 环境启动。它的失败只报告给该命令,绝不阻断主开发流程。启动后,其 Mind 授权模拟默认监听 `127.0.0.1:8787`,独立 Bright 联调页默认监听 `127.0.0.1:8788`。Bright 通过 `MIND_AUTH_BASE_URL` 访问模拟的两个授权接口,不再需要 `ONETALK_DEV_*` 身份 fixture;该工具不连接 Mind 数据库,也不属于生产授权实现。
联调页的“上传本地文件到 OSS”把原始文件 POST 给 Bright,再由 server 使用本地环境中的 `OSS_*` 凭据 PUT 到 `harness-uploads/<uuid>/<fileName>`;浏览器不会接触 AccessKey,也不依赖 OSS 浏览器 CORS。成功后页面展示 30 分钟有效的 HTTPS 地址,并自动填入图片或附件发送字段。运行时必须让 `MIND_PAGE_ORIGIN` 与联调页 Origin 精确一致;若四项 `OSS_*` 都未配置,接口返回 `503 oss_upload_unavailable`,不会伪造地址。
本地 `.env.local` 至少应将 `MIND_AUTH_BASE_URL` 指向 mock,并将 `MIND_PAGE_ORIGIN` 指向 Bright 页面地址:
```dotenv
MIND_AUTH_BASE_URL=http://127.0.0.1:8787
MIND_PAGE_ORIGIN=http://127.0.0.1:8788
ONETALK_PLUGIN_ORIGINS=chrome-extension://ogdbffjakeeidblabkeakakdecfbcmlf
```
`apps/chrome-extension/.keys/extension-private-key.pem` 是本地生成的扩展私钥,权限应保持为 `600`,不会提交到 Git;生成的 `dist/manifest.json` 内置从该私钥导出的固定公钥,因此从不同目录加载 `apps/chrome-extension/dist/` 不会再导致扩展 ID 变化。当前固定 ID 是 `ogdbffjakeeidblabkeakakdecfbcmlf``ONETALK_PLUGIN_ORIGINS` 仍必须是这个扩展的精确 Origin,不能改成任意 Origin`MIND_PAGE_ORIGIN` 必须是浏览器打开 `mind-test-harness` 页面时的精确 Origin,而不是 Mind 授权模拟的监听地址。
开发 server 会输出 `[mind-auth][diagnostic]`,只包含 `binding/session` endpoint、授权 operation、`request_started/allowed/rejected/request_failed`、HTTP status 和稳定 code,可据此判断请求是否发出、Mind 是否返回以及最终拒绝原因;不包含 Cookie、binding、请求体或原始异常。扩展握手的 Origin 拒绝会出现在 `[onetalk][diagnostic]` 中。
### Mind/Bright 生产认证边界
生产部署使用同一二级域名下的不同三级域名,例如 `mind.example.com``bright.example.com`。Mind 登录 Cookie 由 Mind 配置为覆盖两个三级域名的 `Domain=.example.com``Path=/``Secure``HttpOnly` CookieMind 页面调用 Bright HTTP 时使用 `credentials: "include"`Bright 只允许精确 Mind Origin,并对页面 WS 和扩展 WS 分别执行 Origin allowlist。
Bright 不连接 Mind 数据库、不读取认证视图、不共享 Mind 登录密钥,也不签发 `mind_page` credential。Bright 只把页面请求中的 Cookie 转发给 Mind 的 Session 授权接口,并把插件的 `channelAccountId + binding` 转发给 Mind 的 binding 授权接口;Cookie 不进入 URL、WS payload、日志或持久化数据。
### Mind 本地测试支架
`mind-test-harness` 是仅供手工测试的独立开发工具:它模拟两个 Mind 授权接口,并提供 Bright HTTP/WS 联调页。Mind 模拟默认监听 `127.0.0.1:8787`,默认 fixture 是账号 `243340382``286995452`、binding `binding-123` 和 Cookie `mind_session=mock-valid``MIND_MOCK_CHANNEL_ACCOUNT_ID` 支持逗号分隔的账号列表,设置 `MIND_MOCK_*` 环境变量后重启即可模拟 binding/version/cookie 切换。两个 endpoint 还可独立设置结果:`MIND_MOCK_BINDING_RESULT=allow|scope_mismatch|binding_revoked|authorization_unavailable``MIND_MOCK_SESSION_RESULT=allow|auth_required|scope_mismatch|authorization_rejected|authorization_unavailable`
根目录执行 `pnpm dev` 时不会启动支架;需要手工验证时才另开终端在开发环境运行 `NODE_ENV=development pnpm dev:harness`。支架不被任何 workspace 包依赖,不含测试文件,不声明 build 脚本,也不进入根 `dev``typecheck``test``build` 或生产 Docker 镜像。支架错误只在其独立命令中报告,不能改变主流程结果。
直接验证两个接口:
```bash
curl -sS http://127.0.0.1:8787/internal/bright/onetalk/authorize-binding \
-H 'content-type: application/json' \
--data '{"channelAccountId":"286995452","binding":"binding-123"}'
curl -sS http://127.0.0.1:8787/internal/bright/onetalk/authorize-session \
-H 'content-type: application/json' \
-H 'cookie: mind_session=mock-valid' \
--data '{"channelAccountId":"286995452"}'
```
手工验证时只允许在开发环境单独运行:
```bash
NODE_ENV=development pnpm dev:harness
```
`pnpm --filter @trade-message-center/server dev:mind-http` 是仅供显式测试配置使用的 server 启动脚本;普通 `pnpm dev` 已直接使用 development-only 的 loopback Mind HTTP 配置。支架不模拟真实 Mind 的 Cookie Domain/SameSite/Secure、三级域 CORS/TLS 或 takeover 事务;它只在独立本地页面中暴露 Bright 的手工操作入口。
`mind-test-harness` 仅允许在开发环境参与显式的 `NODE_ENV=development pnpm dev:harness`;它不进入根 `pnpm dev``typecheck``test``build`,也不会被 `Dockerfile.server` 复制到生产镜像。完成支架改动无需新增或执行测试;若出现错误,只报告,不阻断主流程。
`VITE_BRIGHT_WEBSOCKET_URL` 用于 Chrome 扩展构建:
```dotenv
VITE_BRIGHT_WEBSOCKET_URL=ws://127.0.0.1:7878/ws/plugin
```
它会在扩展构建时注入,必须使用 `ws://``wss://`。开发或生产构建未设置该变量时会直接失败,不再回退到代码内置地址。
该变量是四个扩展入口共用的构建配置,应放在根目录 `.env.local`,或通过命令行/CI 环境变量注入。
只有以 `VITE_` 开头的变量会暴露给扩展代码,数据库连接串和 binding 等服务端敏感配置不得使用该前缀。
### Chrome 扩展下载链接
`GET /api/downloads/chrome-extension` 返回当前 workspace 完整版本的临时 OSS 下载地址:
```json
{
"version": "0.6.1",
"downloadUrl": "https://sinanpilot-bucket.oss-cn-hangzhou.aliyuncs.com/chrome-extension/0.6.1/trade-message-center-chrome-extension-0.6.1.zip?..."
}
```
版本由根 `package.json``version` 经 CI 注入为 `TMC_PACKAGE_VERSION`,接口和发布目录都不使用 `v` 前缀。OSS 签名参数由服务端签名器计算;访问密钥只保存在服务端运行时环境中。未配置 OSS 访问密钥时,接口明确返回 `503 extension_download_unavailable`,不影响本地其它 server 功能。
## Workspace 版本
根目录 `package.json``version` 是 workspace 发布版本的唯一编辑点。根目录的 `dev``dev:extension``build``typecheck``test``format:check` 会自动先执行版本镜像同步,再检查一致性;修改根版本后可以直接运行这些命令,不会因为子包镜像尚未更新而中断。
如需手动执行或单独审计,可使用:
```bash
pnpm version:sync
pnpm version:check
```
也可以用一次命令完成“同步后检查”:
```bash
pnpm version:ensure
```
版本必须是 Chrome 扩展和 package metadata 都支持的三段数字版本,例如 `0.1.0`;不使用 prerelease、build metadata 或额外段。协议、配置、IndexedDB 和授权版本仍是独立概念,不随 workspace 版本修改。
### 准备与发布
在已基于最新 `origin/main` 的发布分支运行下列命令。它默认递增 patch;也可传入 `minor``major`
```bash
pnpm release
pnpm release -- minor
pnpm release -- major
```
该命令要求工作区干净,依次递增根版本、同步所有 workspace package 镜像、运行格式/迁移/类型/测试/构建门禁,确认没有其它持久化文件被修改后,提交 `chore: release <version>` 并推送当前发布分支。若 commit 前任一步失败,脚本会恢复它写入的版本文件;若 commit 已完成但 push 失败,则保留本地提交并明确报错。不要手工改子包版本或 `dist/manifest.json`
发布 PR 合入并让本地 `main``origin/main` 完全一致后,执行:
```bash
pnpm release:publish
```
该命令只创建并推送 `v<version>` tag;现有 tag CI 负责后续构建、数据库门禁、扩展上传和服务部署。
## 启动 server 与数据库
首次启动或数据库结构有变化时,先执行迁移:
```bash
pnpm --filter @trade-message-center/server db:migrate
```
如需重置本地开发数据库,执行以下命令:
```bash
pnpm --filter @trade-message-center/server db:reset
```
该命令会删除开发库的 `public``drizzle` schema,重建 `public` schema,并重新执行全部 migration。它只接受 `NODE_ENV=development` 和 loopback 主机(`localhost``127.0.0.1``::1`)的 `DATABASE_URL`;请确保目标是专用的本地开发库,命令不会保留其中的数据。
再从仓库根目录启动 server 和扩展开发构建:
```bash
pnpm dev
```
根命令会统一生成并注入 `BUILD_HASH`,同时启动 server 和 Popup、MAIN world、ISOLATED world、Service Worker 四个扩展 watch 构建。因此扩展开发不要直接运行底层 Vite 命令。
如果只需要重启 Bright server,使用:
```bash
pnpm dev:server
```
该命令直接启动 server watch,不调用 workspace 级 `with-build-hash`,也跳过 server `predev` 的 contract 构建;因此不会重新构建 Chrome 扩展,也不会改变当前扩展产物中的 `BUILD_HASH`。首次 checkout 或修改 `onetalk-contract` 后,请先执行 `pnpm --filter @trade-message-center/onetalk-contract build`。如果 server 需要本地 Mind 测试支架,请另开终端在开发环境运行 `NODE_ENV=development pnpm dev:harness`
检查 server 是否启动:
```bash
curl http://127.0.0.1:7878/health
```
预期返回:
```json
{ "status": "ok" }
```
本地 Bright 联调页由测试支架独立提供:
```text
http://127.0.0.1:8788/
```
本地 harness 中的 OneTalk account 应包含在 `MIND_MOCK_CHANNEL_ACCOUNT_ID`(默认 `243340382,286995452`)中;生产 Mind 页面不提交可信的 user/workspace header,而由 Cookie Session 授权结果派生。页面会连接配置的 Bright 地址的 `/ws/mind`,插件使用 Bright 的 `/ws/plugin``MIND_PAGE_ORIGIN` 必须设为该页面的 Origin。
## 加载和配置 Chrome 扩展
1. 执行 `pnpm dev`,确认生成 `apps/chrome-extension/dist/`
2. 打开 `chrome://extensions`,启用“开发者模式”,选择“加载已解压的扩展程序”,加载 `apps/chrome-extension/dist/`
3. 打开 OneTalk 页面 `https://onetalk.alibaba.com/`,点击扩展图标打开 Popup;Popup 会从页面运行时读取登录人账号 ID(`currentUserAccountId`),不要填写 URL 的 `activeAccountId`
4. 在 Binding 输入框填入 `MIND_MOCK_BINDING`(默认 `binding-123`)对应的值后保存配置;Popup 首次运行会自动生成 Device ID。URL 中的 `activeAccountId` 仅是当前对话账号。
5. Bright WebSocket URL 仅展示当前构建使用的地址,不在 Popup 中手工填写。配置保存或清除后,Service Worker 会动态建立、重建或关闭 Bright 连接和同步引擎。
扩展配置保存在 `chrome.storage.local`。Popup 不配置 `mindUserId``workspaceId`;保存 binding 时留空表示保持已有 binding,输入新值才会替换它。清除配置不会重置自动生成的 Device ID。
生产构建:
```bash
pnpm build
```
构建完成后仍可将 `apps/chrome-extension/dist/` 作为未解压扩展加载。
## 生成并发布 Chrome 扩展
推送指向 `main` 历史的 Git tag 后,生成流程会在发布前检查中的同一次构建基础上打包扩展,并将版本包上传到 OSS:
```text
oss://<OSS_BUCKET>/chrome-extension/<package-version>/trade-message-center-chrome-extension-<package-version>.zip
```
OSS Bucket、Endpoint 和 Region 已直接写在 `.github/workflows/release_ci.yml` 中。`production` Environment 只需要配置以下密钥:
- Secrets`OSS_ACCESS_KEY_ID``OSS_ACCESS_KEY_SECRET`
上传账号只需要目标 Bucket 对应版本目录的写权限。Git tag 仍用于触发发布和定位源码,但不会作为扩展发布版本;版本目录和文件名使用根 `package.json``version`,均不添加 `v` 前缀。现有同版本覆盖语义保持不变。
PR 和 `main` 分支推送本身不触发发布。发布新版本时先更新根版本并同步子包,合入 `main` 后再推送对应 tag;不要只更换 tag 名而重复使用旧的根版本。CI 自动构建并打包,不需要提交本地 `dist/` 或手动上传 ZIP。
发布顺序为质量检查 → 上传扩展 ZIP 到 OSS → 部署 server。上传失败时不会部署新服务,避免下载接口指向尚不存在的新版本包。扩展使用 `production` Environment 的 `VITE_BRIGHT_WEBSOCKET_URL` 构建;本地 `.env` 中的开发地址不能用于线上下载包。上线后应实际访问下载接口并下载 ZIP,核对 `manifest.json` 版本及扩展连接地址,不能只以 `/health` 成功判断下载功能可用。
## Server 数据库迁移
server 使用 Drizzle 的 TypeScript schema 生成版本化 SQL migration。业务表定义放在 `apps/server/src/database/schema/`,迁移文件和元数据放在 `apps/server/drizzle/`,相关命令从仓库根目录执行:
```bash
pnpm --filter @trade-message-center/server db:generate
pnpm --filter @trade-message-center/server db:check
pnpm --filter @trade-message-center/server db:migrate
```
生产部署时,先以独立的一次性 job 执行 `db:migrate`,成功后再启动或滚动更新 server;应用启动不会自动修改数据库结构。已执行的 migration 不可修改,破坏性变更使用新的补偿 migration 和 expand-contract 流程。
## 测试与质量检查
常用检查:
```bash
pnpm format:check
pnpm typecheck
pnpm test
pnpm build
git diff --check
```
## VSCode 开发环境
本项目统一使用 Oxfmt 格式化代码,缩进为 4 个空格。请在 VSCode 中安装 Oxc 插件 `oxc.oxc-vscode`,否则保存文件时可能使用其它 formatter,导致代码在提交钩子中再次变化。
安装方式:
1. 打开 VSCode 扩展面板(macOS 快捷键:`⇧⌘X`)。
2. 搜索 `Oxc`,安装扩展 `oxc.oxc-vscode`
3. 重新打开项目窗口,确认右下角或状态栏使用 Oxc formatter。
仓库已经在 `.vscode/settings.json` 中配置 Oxc 保存格式化,并通过 `.oxfmtrc.json` 固定 4 空格规则。项目本地依赖已经包含 Oxfmt,不需要单独全局安装。
如果暂时不安装插件,请关闭 VSCode 的 `formatOnSave`,需要格式化时在项目根目录执行:
```bash
pnpm format
pnpm format:check
```
不要同时启用 Prettier、Biome 或 VSCode 内置 TypeScript formatter。
1213