mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
361 lines
20 KiB
Markdown
361 lines
20 KiB
Markdown
# 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` Cookie;Mind 页面调用 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
|