19 changed files with 369 additions and 42 deletions
@ -0,0 +1,103 @@ |
|||||
|
# CLAUDE.md |
||||
|
|
||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
||||
|
|
||||
|
## 项目概览 |
||||
|
|
||||
|
`yunyan-sas` 是一个从单产品服务 `deep_server`(Go 模块名 `earphone`)迁移而来的 **monorepo SaaS 平台**,目标是把单产品后端改造成可托管多个应用/产品的 SaaS。迁移时 Go 模块名 `earphone` → `yunyan`,所有 import 前缀随之变更。回复一律用中文。 |
||||
|
|
||||
|
## 仓库结构 |
||||
|
|
||||
|
``` |
||||
|
yunyan-sas/ |
||||
|
├── go.work # Go workspace:use ./apps/services |
||||
|
├── build.sh # 构建/推送镜像(被 deploy.sh 调用,也可单用) |
||||
|
├── apps/ |
||||
|
│ ├── proto/ # .proto 源文件(按业务域分目录) |
||||
|
│ ├── services/ # Go 后端(module: yunyan, go 1.25.0) |
||||
|
│ │ ├── lego/ # 内部服务框架(IService/IModule/Comp + sys 子系统) |
||||
|
│ │ ├── sys/ # 第三方集成(aliyun/tencentyun/openai/coze/nats/...) |
||||
|
│ │ ├── comm/ # 跨模块公共:常量、ErrorCode、IUserSession、NATS key |
||||
|
│ │ ├── modules/ # 业务模块(每个 = 一组 Comp) |
||||
|
│ │ ├── services/ # 各服务的 main.go 入口 + conf/*.yaml |
||||
|
│ │ ├── pb/ # protoc 生成的 *.pb.go(DO NOT EDIT) |
||||
|
│ │ ├── swagger/、utils/ |
||||
|
│ ├── admin/ # 管理后台前端(Nuxt 4 SSR + Tailwind + DaisyUI 4 + Pinia) |
||||
|
│ └── web/ # 用户侧前端骨架(Nuxt 4) |
||||
|
└── deploy/<service>/ # 每服务一份 deploy.sh + docker-compose.yml + env/ |
||||
|
``` |
||||
|
|
||||
|
## 常用命令 |
||||
|
|
||||
|
### Go 后端(在 `apps/services/` 下执行) |
||||
|
|
||||
|
```bash |
||||
|
go build ./... # 编译全部 |
||||
|
go vet ./... # 静态检查 |
||||
|
go test ./... # 跑全部测试(测试集中在 sys/* 与 utils/) |
||||
|
go test ./sys/sms/ -run TestXxx -v # 跑单个测试 |
||||
|
go build -o /tmp/console ./services/console/ # 编译单个服务 |
||||
|
go run ./services/console -conf ./services/console/conf/console.yaml # 本地起服务 |
||||
|
``` |
||||
|
|
||||
|
服务列表:`api`、`gateway`、`home`、`mcp`、`migu`、`timer`(集群服务,走 ETCD/rpcx)、`console`(单例服务,不接 ETCD)。每个服务 `main.go` 用 `-conf` 指向自己的 `conf/*.yaml`,默认相对路径 `./conf/<svc>.yaml`。 |
||||
|
|
||||
|
### Admin 前端(在 `apps/admin/` 下执行) |
||||
|
|
||||
|
```bash |
||||
|
pnpm install |
||||
|
pnpm dev # 开发,默认 :3000 |
||||
|
pnpm build # 产出 .output/(SSR) |
||||
|
``` |
||||
|
|
||||
|
本地开发需同时起 console 后端(默认代理到 `127.0.0.1:8080`,见下文 proxy)。超管账号见各 `console.yaml` 的 `AdminAccount/AdminPassword`。 |
||||
|
|
||||
|
### 构建镜像与部署 |
||||
|
|
||||
|
```bash |
||||
|
./build.sh console # 构建 docker-registry.../yunyan-console:latest(PUSH=0 只构建不推) |
||||
|
./build.sh admin |
||||
|
cd deploy/console && ./deploy.sh prod # 一键发布:构建推送 + 同步配置 + 远程 pull + up |
||||
|
cd deploy/console && ./deploy.sh local up # 本机起容器 |
||||
|
cd deploy/console && ./deploy.sh prod logs # 看日志 |
||||
|
``` |
||||
|
|
||||
|
`deploy.sh <env> [action]`:`env` = `local|dev|prod`;`action` dev/prod 默认 `deploy`,local 默认 `up`,另有 `up/build/pull/down/restart/logs/ps`。服务名取自所在目录名。 |
||||
|
|
||||
|
## 后端架构(lego 框架) |
||||
|
|
||||
|
整套后端围绕 `lego` 自研框架,核心是 **Service → Module → Comp** 三层 + 反射注册路由。 |
||||
|
|
||||
|
- **启动**:`lego.Run(service, modules...)`([lego/lego.go](apps/services/lego/lego.go))依次 `Init → Start → Run → Destroy`。 |
||||
|
- **两种 Service 基类**: |
||||
|
- `lego/base/rpcx`(集群服务):接 ETCD 做服务发现,服务间用 rpcx RPC 互调。`api/gateway/home/mcp/migu/timer` 用它,且 `OnInstallComp` 装 `services.NewHttpRouteComp()`。 |
||||
|
- `lego/base/single`(单例服务):**不接 ETCD/集群**,只驱动单个模块。`console` 用它,主库 Supabase Postgres + 独立 Redis + NATS,维护 `app_registry` 注册表,按"选中应用"动态建到目标应用库的连接。 |
||||
|
- **Module → Comp**:模块(如 `modules/api`)在 `OnInstallComp` 里 `RegisterComp` 一组组件:`apiComp`(HTTP 入口)、`modelComp`(数据访问)、各种 consumer/interceptor。见 [modules/api/module.go](apps/services/modules/api/module.go)。 |
||||
|
- **HTTP 路由靠反射自动注册**([modules/comp_httpgate.go](apps/services/modules/comp_httpgate.go)):继承 `modules.MCompHttpGate` 的组件,其**每个导出方法**只要签名是 |
||||
|
```go |
||||
|
func (c *apiComp) MethodName(session comm.IUserSession, req *pb.XxxReq) (*pb.XxxResp, *pb.ErrorData) |
||||
|
``` |
||||
|
就会被注册成路由 `<module>_<methodname小写>`(如 `api_getfactorys`)。新增接口 = 新建 `modules/<mod>/api_xxx.go` 加一个符合签名的方法即可,**无需手动注册**。后缀 `Check` 的方法不注册。 |
||||
|
- **错误处理约定**:成功填 `resp`;失败构造 `&pb.ErrorData{Code: pb.ErrorCode_XXX, Message: ...}` 并 `return`。 |
||||
|
- **pb 包**:`pb/*.pb.go` 由 `apps/proto/` 下 `.proto` 经 `protoc` + `protoc-gen-go` v1.36 生成,**手动改 .pb.go 无效**;改协议要改 `.proto` 再重新生成。注意 console 模块有本地 gorm struct(非 pb)的 model 文件,见 `modules/console/model_*.go`。 |
||||
|
|
||||
|
## Admin 前端约定(关键,易踩坑) |
||||
|
|
||||
|
- **API 代理**:[server/middleware/proxy.ts](apps/admin/server/middleware/proxy.ts) 把 `/web/api/*` 和 `/console/api/*` 代理到 `CONSOLE_BACKEND`(容器内由 env 注入,本地默认 `http://127.0.0.1:8080`)。 |
||||
|
- **`useApi().webApi(method, data)`**([app/composables/useApi.ts](apps/admin/app/composables/useApi.ts)): |
||||
|
- 已自动拼成 `/web/api/api_${method}` 并**已解包 `data`**(成功返回 `data` 本体;失败 `throw`;`code:18` 自动登出)。 |
||||
|
- ⚠️ 调用时 `method` **不要带 `api_` 前缀**,否则变成 `api_api_xxx` → `code:11`。 |
||||
|
- 方法名以 `get/list/load/query/fetch/search/count/stat/export/check` 开头视为只读,不弹全屏遮罩;其余(add/update/del/save/create…)视为写操作,请求期间套遮罩防重复提交。 |
||||
|
- 选中应用通过请求头 `X-App-Id` 传给后端。 |
||||
|
- **后端返回常多包一层**:如 `getfactorys → {factorys}`、`getproducts → {products}`、`getproduct → {product}`;保存 payload 也常包一层(厂家 `{Factory:{...}}`、产品 `{product:{...}}`);统计 trend 日期字段是 `stat_day`。列表 ID 多以十六进制展示(`id.toString(16).toUpperCase()`)。 |
||||
|
- 样式:`app/assets/css/console.css` 由 nuxt.config 引用;DaisyUI 固定 4.x。 |
||||
|
|
||||
|
## 配置与密钥约定(重要) |
||||
|
|
||||
|
- 各 `conf/*.yaml` 与 `deploy/<svc>/deploy.sh` 中**写死了真实的 DSN / 密钥 / 服务器 IP / 仓库密码,且纳入 git**——这是用户在私有仓库下**明确接受**的取舍。修改这些文件时注意它们会随仓库提交。 |
||||
|
- ⚠️ **本仓库若要公开,必须先清理** `deploy.sh` 的 `deploy_profile`(`DEPLOY_HOST`/`REGISTRY_PASS`)与各 `*.yaml` 里的真实凭据。 |
||||
|
- 改部署逻辑要同步两份 `deploy/console/deploy.sh` 和 `deploy/admin/deploy.sh`(内容相同,建议改一份再 cp 覆盖)。 |
||||
|
|
||||
|
## 迁移上下文 |
||||
|
|
||||
|
源项目在 `/Users/liwei/work/go/yunyan/deep_server_up/`(及 `go_earphone/server/deep_server/`)。从旧代码移植接口时,注意 import 路径由 `earphone/...` 改为 `yunyan/...`,并核对接口签名是否符合上文反射注册约定。 |
||||
@ -0,0 +1,64 @@ |
|||||
|
# CLAUDE.md |
||||
|
|
||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
||||
|
|
||||
|
> 根 [CLAUDE.md](../../../../CLAUDE.md) 描述整体架构(lego 框架、反射注册路由、pb 生成);本文件只补充 `echomeet` 模块自身。 |
||||
|
|
||||
|
## 模块职责 |
||||
|
|
||||
|
`echomeet` 是「会议记录」模块:上传音频 → **转写(Transcribe)** → **翻译(Translate)** → **AI 总结(Summary)**,产出 `overview`(短总结)+ `summary`(按模板的完整纪要)。由 `home` 服务装载运行([services/home/main.go](../../services/home/main.go)),不是独立服务。HTTP 接口经反射注册为 `echomeet_<方法名小写>`,前缀路由示例 `/api/home/echomeet_addrecord`。 |
||||
|
|
||||
|
## 核心设计:一会一服务商,三段同源 |
||||
|
|
||||
|
每条会议记录在 [providers.go](providers.go) 的 `ResolveServiceType` 解析出**唯一服务商**,转写/翻译/总结三步都走它,结果记在 `record.ServiceType`。解析优先级: |
||||
|
|
||||
|
1. 请求显式 `service_type` → |
||||
|
2. 模块配置 `DefaultService` → |
||||
|
3. 兜底按源语言:字节支持该语言走字节,否则阿里。 |
||||
|
|
||||
|
四套服务商,每套三件套(见对应 `provider_*.go`): |
||||
|
|
||||
|
| ServiceType | 转写 | 翻译 | 总结(Summarizer) | |
||||
|
|---|---|---|---| |
||||
|
| ByteDance | `sys/bytedance/audomodel` | 火山翻译 | 豆包 `sys/doubao` | |
||||
|
| Ali | DashScope `sys/aliyun/filetrans` | 阿里 MT | 通义千问(`sys/openai` 兼容端点)| |
||||
|
| Google | Cloud Speech | Cloud Translation | Gemini `sys/google/gemini` | |
||||
|
| Microsoft | Azure Speech | Azure Translator | Azure OpenAI(`sys/openai` 兼容端点)| |
||||
|
|
||||
|
三类能力抽象为 `Transcriber` / `Translator` / `Summarizer` 接口(providers.go),运行期 `GetTranscriber/GetTranslator/GetSummarizer(svcType, language)` 按 ServiceType 路由。新增服务商 = 写一个 `provider_xxx.go` 实现三接口 + 在 `providersComp.Init` 注册进三张 map。 |
||||
|
|
||||
|
## 组件与异步流水线 |
||||
|
|
||||
|
`module.go` 装配组件:`api`(HTTP) / `model`(gorm) / `cache`(模板缓存) / `providers` / `tasks`。 |
||||
|
|
||||
|
[tasks.go](tasks.go) 是异步引擎:**Redis 队列 + ants 协程池 + cron 每秒调度**。 |
||||
|
|
||||
|
- 两条流水线,各有「等待队列 / 在执行集合」两个 list:短音频转写(`keyShortAwait/Proc`)、AI 总结(`keyAIAwait/Proc`)。队列 key 用 `service.GetTag()` + `redissys.RKey`(应用分组前缀)命名空间隔离,多应用共用一个 Redis 不串扰。 |
||||
|
- `TaskScheduling`(cron `*/1 * * * * ?`)每秒把等待队列搬进执行集合(受 `MaxShortAudioProcess`/`MaxAIAwaitProcess` 限流),再 `pool.Invoke` 真正处理。 |
||||
|
- **转写完成两条路径**:异步回调(字节 [api_backcall.go](api_backcall.go) / 阿里 [api_alibackcall.go](api_alibackcall.go))+ 客户端拉取时主动轮询(`PollTranscribe`,由 [api_getrecord.go](api_getrecord.go)/[api_getrecords.go](api_getrecords.go) 在状态为 Transcribing 时驱动,`TranscribeQueryMinInterval` 限频)。**没有 cron 兜底轮询**。 |
||||
|
- 短音频走字节 flash 同步通道(`ShortAudioProcess`);长音频/其他服务商走异步 submit+query。 |
||||
|
|
||||
|
状态机见 `DBEchoMeetRecordState`(proto):Unknow→AwaitTranscribing→Transcribing→AwaitSummarizing→Summarizing→Completed→Readed,失败 TranscribeFail/SummarizFail。转写失败会**退还** `Meetintegral` 时长并冲正统计埋点。 |
||||
|
|
||||
|
## 语言码映射(core.go,易错) |
||||
|
|
||||
|
输入统一是客户端 BCP-47(`zh-CN`/`en-US`…)。每个平台格式不同,core.go 逐家维护 map。**铁律:未识别语言一律返回原值,绝不 fallback 成 `zh`**——否则「未知语言→中文」会藏在调用栈深处。翻译时在 `TranslateProcess` 直接传 BCP-47,由各 provider 内部转换,不要提前转(会被二次转换误判)。 |
||||
|
|
||||
|
## AI 总结输入约定(含图片) |
||||
|
|
||||
|
`AIProcess`(tasks.go)拼 `originalText`:把 `translate`(已翻译的分句)按 `[说话人]:内容` 拼接;若 `record.Remark`(用户创建时填的备注)非空,用语言中立的结构化标签包裹:`<remark>…</remark>\n\n<transcript>\n…\n</transcript>`——**不要硬编码某语言的提示词**,避免污染多语言总结。模板 `template.Template`/`template.Outline` 作为 system prompt,分别产出 `summary`/`overview`(两路并发)。 |
||||
|
|
||||
|
**图片注释(支持多张)**:请求 `EchomeetAddRecordReq.imageurls` 是 `repeated string`,DB `DBEchoMeetRecord.imageurls` 是 **string 列存 JSON 数组**(编码方式同 `original`:`utils.ToString` 写、`json.Unmarshal` 读)。`AIProcess` 解析出 url 列表后,作为多模态内容随 user 消息一并发给总结模型(要求所配模型支持视觉)。链路:`ChatMessage.Images []string` → 各 `Summarizer.Chat` → sys 层(豆包/通义/Azure 用 OpenAI 风格 `image_url` 直传;Gemini 在 `sys/google/gemini` 内逐张下载转 base64 内联 `inlineData`,单张失败仅跳过)。列表为空时完全走原文本路径,不影响既有行为。 |
||||
|
|
||||
|
## 重新生成 pb |
||||
|
|
||||
|
改了 `apps/proto/echomeet/*.proto` 后需重生成(protoc-gen-go v1.36.6,已安装),并复刻源项目 `pb.py` 的 `@go_tags` 注入(覆盖 json 去掉 omitempty、追加 gorm/自定义 tag): |
||||
|
|
||||
|
```bash |
||||
|
cd apps/services |
||||
|
protoc --go_out=pb --go_opt=paths=import -I ../proto ../proto/echomeet/echomeet_db.proto |
||||
|
protoc --go_out=pb --go_opt=paths=import -I ../proto ../proto/echomeet/echomeet_msg.proto |
||||
|
# 再对生成的 pb/echomeet_*.pb.go 执行 @go_tags 注入(见根 CLAUDE.md / 源项目 pb.py) |
||||
|
``` |
||||
|
|
||||
|
注意:HTTP 层用 jsoniter 按 **json tag** 收发、DB 用 **gorm tag**,protobuf 二进制 wire 不参与这些 API,所以字段的 json/gorm tag 必须正确。 |
||||
Loading…
Reference in new issue