Files
trade-message-center/README.md
2026-09-16 22:16:17 +08:00

20 KiB
Raw Permalink Blame History

Trade Message Center

本地开发要求

  • Node.js >=22.22.2 <23
  • pnpm 11.7.0
  • 可连接的 PostgreSQL 数据库
  • Chrome 或 Chromium(用于加载和调试扩展)

安装依赖:

pnpm install

GitNexus 代码智能

GitNexus 会把代码解析为符号、调用关系和执行流程图,适合在改动前定位实现、评估影响范围,以及在提交前核对实际波及的模块。索引保存在本机的 .gitnexus/,不会提交到 Git。

安装与首次建索引

本仓库已经生成过 .gitnexus/run.cjs;日常使用不需要全局安装。新 clone 没有该文件时,在仓库根目录执行一次:

npx gitnexus@latest analyze

若本机的 npm 11 在安装原生依赖时失败,改用 pnpm:

pnpm --allow-build=@ladybugdb/core --allow-build=gitnexus --allow-build=tree-sitter \
  dlx gitnexus@latest analyze

也可以安装全局命令,之后上述本地 runner 会优先使用它:

npm install -g gitnexus

索引维护

在进行较大代码变更后、GitNexus 报告索引过期时,或需要重新生成 AI 指引文件时,执行:

node .gitnexus/run.cjs analyze

常用维护命令:

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

开发环境可以从模板开始:

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_BUCKETOSS_ENDPOINT Chrome 扩展发布包及本地联调上传所用的 OSS Bucket 与 HTTPS Endpoint。
OSS_ACCESS_KEY_IDOSS_ACCESS_KEY_SECRET 仅服务端使用的 OSS V1 下载签名与联调页 server-proxy PUT 凭据。

本地默认配置如下:

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 页面地址:

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 是 ogdbffjakeeidblabkeakakdecfbcmlfONETALK_PLUGIN_ORIGINS 仍必须是这个扩展的精确 Origin,不能改成任意 OriginMIND_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.combright.example.com。Mind 登录 Cookie 由 Mind 配置为覆盖两个三级域名的 Domain=.example.comPath=/SecureHttpOnly 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 是账号 243340382286995452、binding binding-123 和 Cookie mind_session=mock-validMIND_MOCK_CHANNEL_ACCOUNT_ID 支持逗号分隔的账号列表,设置 MIND_MOCK_* 环境变量后重启即可模拟 binding/version/cookie 切换。两个 endpoint 还可独立设置结果:MIND_MOCK_BINDING_RESULT=allow|scope_mismatch|binding_revoked|authorization_unavailableMIND_MOCK_SESSION_RESULT=allow|auth_required|scope_mismatch|authorization_rejected|authorization_unavailable

根目录执行 pnpm dev 时不会启动支架;需要手工验证时才另开终端在开发环境运行 NODE_ENV=development pnpm dev:harness。支架不被任何 workspace 包依赖,不含测试文件,不声明 build 脚本,也不进入根 devtypechecktestbuild 或生产 Docker 镜像。支架错误只在其独立命令中报告,不能改变主流程结果。

直接验证两个接口:

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"}'

手工验证时只允许在开发环境单独运行:

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 devtypechecktestbuild,也不会被 Dockerfile.server 复制到生产镜像。完成支架改动无需新增或执行测试;若出现错误,只报告,不阻断主流程。

VITE_BRIGHT_WEBSOCKET_URL 用于 Chrome 扩展构建:

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 下载地址:

{
    "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.jsonversion 经 CI 注入为 TMC_PACKAGE_VERSION,接口和发布目录都不使用 v 前缀。OSS 签名参数由服务端签名器计算;访问密钥只保存在服务端运行时环境中。未配置 OSS 访问密钥时,接口明确返回 503 extension_download_unavailable,不影响本地其它 server 功能。

Workspace 版本

根目录 package.jsonversion 是 workspace 发布版本的唯一编辑点。根目录的 devdev:extensionbuildtypechecktestformat:check 会自动先执行版本镜像同步,再检查一致性;修改根版本后可以直接运行这些命令,不会因为子包镜像尚未更新而中断。

如需手动执行或单独审计,可使用:

pnpm version:sync
pnpm version:check

也可以用一次命令完成“同步后检查”:

pnpm version:ensure

版本必须是 Chrome 扩展和 package metadata 都支持的三段数字版本,例如 0.1.0;不使用 prerelease、build metadata 或额外段。协议、配置、IndexedDB 和授权版本仍是独立概念,不随 workspace 版本修改。

准备与发布

在已基于最新 origin/main 的发布分支运行下列命令。它默认递增 patch;也可传入 minormajor

pnpm release
pnpm release -- minor
pnpm release -- major

该命令要求工作区干净,依次递增根版本、同步所有 workspace package 镜像、运行格式/迁移/类型/测试/构建门禁,确认没有其它持久化文件被修改后,提交 chore: release <version> 并推送当前发布分支。若 commit 前任一步失败,脚本会恢复它写入的版本文件;若 commit 已完成但 push 失败,则保留本地提交并明确报错。不要手工改子包版本或 dist/manifest.json

发布 PR 合入并让本地 mainorigin/main 完全一致后,执行:

pnpm release:publish

该命令只创建并推送 v<version> tag;现有 tag CI 负责后续构建、数据库门禁、扩展上传和服务部署。

启动 server 与数据库

首次启动或数据库结构有变化时,先执行迁移:

pnpm --filter @trade-message-center/server db:migrate

如需重置本地开发数据库,执行以下命令:

pnpm --filter @trade-message-center/server db:reset

该命令会删除开发库的 publicdrizzle schema,重建 public schema,并重新执行全部 migration。它只接受 NODE_ENV=development 和 loopback 主机(localhost127.0.0.1::1)的 DATABASE_URL;请确保目标是专用的本地开发库,命令不会保留其中的数据。

再从仓库根目录启动 server 和扩展开发构建:

pnpm dev

根命令会统一生成并注入 BUILD_HASH,同时启动 server 和 Popup、MAIN world、ISOLATED world、Service Worker 四个扩展 watch 构建。因此扩展开发不要直接运行底层 Vite 命令。

如果只需要重启 Bright server,使用:

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 是否启动:

curl http://127.0.0.1:7878/health

预期返回:

{ "status": "ok" }

本地 Bright 联调页由测试支架独立提供:

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/pluginMIND_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 不配置 mindUserIdworkspaceId;保存 binding 时留空表示保持已有 binding,输入新值才会替换它。清除配置不会重置自动生成的 Device ID。

生产构建:

pnpm build

构建完成后仍可将 apps/chrome-extension/dist/ 作为未解压扩展加载。

生成并发布 Chrome 扩展

推送指向 main 历史的 Git tag 后,生成流程会在发布前检查中的同一次构建基础上打包扩展,并将版本包上传到 OSS:

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 只需要配置以下密钥:

  • SecretsOSS_ACCESS_KEY_IDOSS_ACCESS_KEY_SECRET

上传账号只需要目标 Bucket 对应版本目录的写权限。Git tag 仍用于触发发布和定位源码,但不会作为扩展发布版本;版本目录和文件名使用根 package.jsonversion,均不添加 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/,相关命令从仓库根目录执行:

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 流程。

测试与质量检查

常用检查:

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,需要格式化时在项目根目录执行:

pnpm format
pnpm format:check

不要同时启用 Prettier、Biome 或 VSCode 内置 TypeScript formatter。

1213