# 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// # 每服务一份 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/.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 # 构建 registry.voitrans.net/starpivot-console:latest(PUSH=0 只构建不推) ./build.sh admin # 测试环境发布(在 deploy/<服务>/ 下,服务 = app | console | admin) cd deploy/console && ./dev-deploy.sh yunyan # 云雁测试环境(广州阿里云 8.138.86.223) cd deploy/console && ./dev-deploy.sh # 不带参数 → 交互选环境:lingpu | yunyan # 生产:先构建带版本号的镜像,再部署 cd deploy/console && ./prod-build.sh 0.2.1 yunyan # 第 2 个参数选公司(yunyan|lingpu),省略则交互选 cd deploy/console && ./prod-deploy.sh 0.2.1 yunyan # 第 2 个参数选生产环境,须与构建时所选公司一致;省略则交互选 ``` **镜像仓库按公司区分**,地址/账号统一在 [deploy/registry-profiles.sh](deploy/registry-profiles.sh)(换仓库只改这一处,各脚本 source 它): - `yunyan` → `registry.voitrans.net`(仓库实体在广州机 8.138.86.223) - `lingpu` → `registry.lingpu.net`(灵谱香港机) **生产镜像名也按公司区分**,映射同在 `registry-profiles.sh` 的 `prod_image_name()`(build 与 deploy 共用一份,防漂移): admin/console 两家都用 `starpivot-*`(与 `docker-compose.yml` 里写死的 image/container_name 一致); 只有云雁的 app 仍是 `voitrans-a11`(上海机正跑着 7 个该镜像的容器,改名 compose 会认不出)。 admin/console 的 `prod-deploy.sh` 已改为**多生产环境档案**(`env_profile()`,同 `dev-deploy.sh` 的写法): - `yunyan` → 139.224.212.237(上海阿里云,与业务后端 app 同机),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/Documents/key/aliyun_shanghai.pem` (2026-08-05 由美国机 47.253.88.66 迁来;美国机上的 `yunyan-admin`/`console-a11` 容器仍在跑,待验证后停,脚本已不再指向它) - `lingpu` → 8.166.114.65(广州阿里云),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/.ssh/lingpu-gz.pem` `dev-deploy.sh <环境>` 只做镜像部署:构建推送 → 远端 pull 重启,**不上传** 服务器上的真实 `.env` / `confs/*.yaml`(只下发 `*.example` 模板)。 ## 后端架构(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) ``` 就会被注册成路由 `_`(如 `api_getfactorys`)。新增接口 = 新建 `modules//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//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/...`,并核对接口签名是否符合上文反射注册约定。