11 KiB
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/ 下执行)
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/ 下执行)
pnpm install
pnpm dev # 开发,默认 :3000
pnpm build # 产出 .output/(SSR)
本地开发需同时起 console 后端(默认代理到 127.0.0.1:8080,见下文 proxy)。超管账号见各 console.yaml 的 AdminAccount/AdminPassword。
构建镜像与部署
./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 along # 阿龙测试环境(阿里云 8.133.166.29)
cd deploy/console && ./dev-deploy.sh # 不带参数 → 交互选环境:lingpu | yunyan | along
# 生产:先构建带版本号的镜像,再部署
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(换仓库只改这一处,各脚本 source 它):
yunyan→registry.voitrans.net(仓库实体在广州机 8.138.86.223)lingpu→registry.lingpu.net(灵谱香港机)along→registry.ymaikj.com(仓库实体就在阿龙机 8.133.166.29 上,账号是admin不是liwei);只用于测试环境,不在生产COMPANIES菜单里
测试环境档案在各 dev-deploy.sh 的 env_profile():
along→ 8.133.166.29(阿里云,x86_64/Docker 24.0.9),目录/home/work/starpivot/{app,admin,console},密钥~/.ssh/along.pem该机已自带 postgres:16 / redis:7 / mysql / NPM / gitea / docker-registry,配.env时优先复用,别另起一套
生产镜像名统一为 starpivot-<服务>(admin/console/app 三个服务、两家公司都一样),映射在
registry-profiles.sh 的 prod_image_name()——build 与 deploy 共用这一份,别在各脚本里写死。
compose 的 image 由脚本注入 IMAGE_NAME 变量,同样不写死。
⚠️ app 的旧名 voitrans-a11 已于 2026-08-10 废弃,仓库里该名下的旧 tag(如 0.1.2)不会自动搬到新名,
换名后首次上线要重新 ./prod-build.sh <version> 推一版;服务器上手工维护的 docker-compose.yml
也要同步成新模板(脚本只下发 .example,不覆盖真实文件)。
注意 voitrans-a11 这个串还用在别的语义上,那些不能动:confs/*.yaml 的 CLUSTER_TAG(etcd 集群标签,
改了服务互相发现不到)、home.yaml 的 ANALYZE_APP_NAME(须与 console 注册表应用名一致)、
prod-deploy.sh APPS 里的远端目录 /home/work/voitrans-a11。
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)依次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。 - HTTP 路由靠反射自动注册(modules/comp_httpgate.go):继承
modules.MCompHttpGate的组件,其每个导出方法只要签名是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-gov1.36 生成,手动改 .pb.go 无效;改协议要改.proto再重新生成。注意 console 模块有本地 gorm struct(非 pb)的 model 文件,见modules/console/model_*.go。
Admin 前端约定(关键,易踩坑)
- API 代理: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):- 已自动拼成
/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传给后端。
- 已自动拼成
- 应用下拉统一走
api_getmyapps(超管/管理员例外,仍用console/api/apps/list):后端按账号绑定下发可选部署。品牌商/渠道商账号的可见应用不靠手工勾选,而是由「名下产品绑定的应用」推导——产品的应用归属存DBProduct.appnames(应用名 CSV,产品管理页多选),见 scope_app.go。 - 后端返回常多包一层:如
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/...,并核对接口签名是否符合上文反射注册约定。