diff --git a/CLAUDE.md b/CLAUDE.md index 103a2dfe..14b2f3a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,7 +89,7 @@ compose 的 image 由脚本注入 `IMAGE_NAME` 变量,同样不写死。 `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` +- `yunyan` → 139.224.212.237(上海阿里云,与业务后端 app 同机),目录 `/home/work/starpivot/{admin,console}`,密钥 `/Users/liwei1dao/work/密钥/release_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` - `along` → 47.116.104.181(阿龙正式机,2026-08-16 补),目录 `/home/work/starpivot/{admin,console}`,密钥 `/Users/liwei1dao/liwei/密钥/along/releae-guangzhou.pem`(与测试机同一把);镜像走阿龙仓库 `registry.ymaikj.com` diff --git a/apps/services/comm/module.go b/apps/services/comm/module.go index 11ece22b..a1755448 100644 --- a/apps/services/comm/module.go +++ b/apps/services/comm/module.go @@ -37,7 +37,7 @@ type StatEvent struct { ChannelId string // 渠道商ID(短码):设备类事件填该设备授权码上的渠道商;其余事件填用户最后绑定设备的渠道商(DBUser.Lastbindchannelid);无归属填 "" Uid string // 触发用户;用于登录/活跃/付费用户的去重统计 - Count int64 // 通用次数,<=0 时按 1 计 + Count int64 // 通用次数:0 视为未指定按 1 计;负数保留,用于冲正(如解绑 -1) Amount int64 // 金额(分):订单类事件 Second int64 // 时长(秒):翻译 / 会议 Words int64 // 字数:翻译 diff --git a/apps/services/modules/mcp/core.go b/apps/services/modules/mcp/core.go index c55bc079..9614b0f7 100644 --- a/apps/services/modules/mcp/core.go +++ b/apps/services/modules/mcp/core.go @@ -2,12 +2,14 @@ package mcp import ( "context" - "yunyan/lego/sys/log" + "fmt" "net/http" "runtime/debug" "time" + "yunyan/lego/sys/log" "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" ) const ( @@ -32,6 +34,42 @@ func stringInArray(target string, array []string) bool { return false } +// safeLogErrorf 记录错误日志,日志系统未初始化等异常不向外扩散 +func safeLogErrorf(format string, args ...interface{}) { + defer func() { _ = recover() }() + log.Errorf(format, args...) +} + +// safeToolHandler 包装工具处理函数,保证任何情况下都给大模型返回一个可读的结果: +// 1. 捕获 handler 内部 panic(参数异常导致的空指针、数组越界等),不让单个工具打崩整条请求; +// 2. 兜底 handler 返回 (nil, nil) 的情况——mcp-go 的 request_handler 会直接解引用结果(*result), +// result 为 nil 时必然 panic; +// 3. 把 error 转成工具错误结果(IsError),让模型能看到失败原因并自行重试/换参数, +// 而不是整条 JSON-RPC 请求失败。 +func safeToolHandler(name string, handler server.ToolHandlerFunc) server.ToolHandlerFunc { + return func(ctx context.Context, request mcp.CallToolRequest) (result *mcp.CallToolResult, err error) { + defer func() { + if r := recover(); r != nil { + //先给出结果,再记录日志,保证日志系统本身出问题也不影响返回 + result = mcp.NewToolResultError(fmt.Sprintf("工具 %s 执行异常,请确认调用参数是否正确后重试", name)) + err = nil + safeLogErrorf("tool %s panic recovered: %v\nstack: %s", name, r, string(debug.Stack())) + } + }() + if result, err = handler(ctx, request); err != nil { + safeLogErrorf("tool %s exec err: %v", name, err) + result = mcp.NewToolResultError(fmt.Sprintf("工具 %s 执行失败: %s", name, err.Error())) + err = nil + return + } + if result == nil { + safeLogErrorf("tool %s return nil result", name) + result = mcp.NewToolResultError(fmt.Sprintf("工具 %s 未返回结果,请确认调用参数是否正确后重试", name)) + } + return + } +} + func isValidDate(dateStr string) bool { _, err := time.Parse("2006-01-02", dateStr) return err == nil diff --git a/apps/services/modules/mcp/module.go b/apps/services/modules/mcp/module.go index 22812540..27188c25 100644 --- a/apps/services/modules/mcp/module.go +++ b/apps/services/modules/mcp/module.go @@ -154,7 +154,7 @@ func (this *Mcp) run() { func (this *Mcp) AddTool(group string, tool mcp.Tool, handler server.ToolHandlerFunc) bool { if tools, ok := this.options.Groups[group]; ok { //不存在 组注册 if stringInArray(tool.Name, tools) { //存在 菜注册 - this.server.AddTool(tool, handler) + this.server.AddTool(tool, safeToolHandler(tool.Name, handler)) return true } } diff --git a/apps/services/modules/mcp/tool_hefeng_weather.go b/apps/services/modules/mcp/tool_hefeng_weather.go index defc5690..bae9129b 100644 --- a/apps/services/modules/mcp/tool_hefeng_weather.go +++ b/apps/services/modules/mcp/tool_hefeng_weather.go @@ -89,28 +89,31 @@ func (this *tool_hefeng_weather) Handl(ctx context.Context, request mcp.CallTool } } if city, err = request.RequireString("city"); err != nil || city == "" { - err = fmt.Errorf("必要参数丢失") + err = nil + result = mcp.NewToolResultError("缺少必要参数 city,请补充需要查询的城市名称后重试") return } - if cityresult, err = hefengweather.CityLookup(city); err != nil || cityresult.Code != "200" { - context = fmt.Sprintf("查询目标城市信息失败 city:%s,请重新确认城市名是否正确", city) + //城市查询失败(网络错误、返回码非200、没有匹配到城市)统一按工具错误返回,交给模型自行纠正参数 + if cityresult, err = hefengweather.CityLookup(city); err != nil || cityresult == nil || cityresult.Code != "200" || len(cityresult.Location) == 0 { log.Error("获取天气信息失败", log.Field{Key: "city", Value: city}, log.Field{Key: "day", Value: day}, log.Field{Key: "err", Value: err}, ) + err = nil + result = mcp.NewToolResultError(fmt.Sprintf("查询目标城市信息失败 city:%s,请重新确认城市名是否正确", city)) return } - weatherresult, err = hefengweather.QueryWeather(cityresult.Location[0].ID) - if err != nil && weatherresult.Code != "200" { - context = fmt.Sprintf("查询目标城市天气信息失败 city:%s,请重新确认城市名是否正确", city) + if weatherresult, err = hefengweather.QueryWeather(cityresult.Location[0].ID); err != nil || weatherresult == nil || weatherresult.Code != "200" { this.module.Debug("tool_hefeng_weather fail", log.Field{Key: "city", Value: city}, log.Field{Key: "day", Value: day}, log.Field{Key: "err", Value: err}, ) + err = nil + result = mcp.NewToolResultError(fmt.Sprintf("查询目标城市天气信息失败 city:%s,请重新确认城市名是否正确", city)) return } else { context = utils.ToString(map[string]interface{}{ diff --git a/apps/services/modules/user/api_binddevice.go b/apps/services/modules/user/api_binddevice.go index e00d98df..c720f2ad 100644 --- a/apps/services/modules/user/api_binddevice.go +++ b/apps/services/modules/user/api_binddevice.go @@ -108,6 +108,17 @@ func (this *apiComp) BindDevice(session comm.IUserSession, req *pb.UserBindDevic for _, v := range divices { if v.License == model.Code { this.module.model.delDevice(v.Id) // 删除设备 + // 接管即原用户被动解绑:按原设备自身的产品/渠道冲正 -1, + // 否则同一台设备每换一次账号就多计一次绑定。 + // 不带 Uid:原用户并未操作,不应被计入当日活跃用户。 + if this.module.analyze != nil { + this.module.analyze.Report(&comm.StatEvent{ + Type: comm.StatEventDeviceBind, + ProductId: uint32(v.Productid), + ChannelId: v.Channelid, + Count: -1, + }) + } } } } @@ -175,9 +186,14 @@ func (this *apiComp) BindDevice(session comm.IUserSession, req *pb.UserBindDevic user.Lastbindproductid = product.Id user.Lastbindchannelid = model.Channelid userSaved := false - // 设备激活 + 绑定埋点 + // 设备激活 + 绑定埋点。 + // 激活是「设备一生一次」的事件:只在 codestate==0 首激时记,换绑/接管不再计—— + // 与上面 model.Usedtime 只在 0→1 时写保持同一口径,否则激活数被换绑次数放大。 + // 绑定是净增流量:这里 +1,解绑/被接管处 -1(见 api_unbinddevice.go 与上面自动解绑分支)。 if this.module.analyze != nil { - this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventDeviceActive, Uid: session.GetUserId(), ProductId: uint32(product.Id), ChannelId: model.Channelid}) + if codestate == 0 { + this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventDeviceActive, Uid: session.GetUserId(), ProductId: uint32(product.Id), ChannelId: model.Channelid}) + } this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventDeviceBind, Uid: session.GetUserId(), ProductId: uint32(product.Id), ChannelId: model.Channelid}) } this.module.Debug("BindDevice", diff --git a/apps/services/modules/user/api_unbinddevice.go b/apps/services/modules/user/api_unbinddevice.go index 7dc3b49b..4b23cf67 100644 --- a/apps/services/modules/user/api_unbinddevice.go +++ b/apps/services/modules/user/api_unbinddevice.go @@ -85,6 +85,18 @@ func (this *apiComp) UnBindDevice(session comm.IUserSession, req *pb.UserUnBindD } return } + // 解绑冲正 -1:绑定数是「当日净增」口径,只加不减会让绑了又解的设备永久留在统计里。 + // 产品/渠道取被解绑设备自身的归属(绑定时固化),保证 +1 与 -1 落在同一分区。 + // 激活不冲正:激活是设备一生一次的事件,解绑也不会把授权码 status 归零。 + if this.module.analyze != nil { + this.module.analyze.Report(&comm.StatEvent{ + Type: comm.StatEventDeviceBind, + Uid: session.GetUserId(), + ProductId: uint32(divice.Productid), + ChannelId: divice.Channelid, + Count: -1, + }) + } resp = &pb.UserUnBindDeviceResp{} return } diff --git a/apps/services/tools/importappgoods/main.go b/apps/services/tools/importappgoods/main.go new file mode 100644 index 00000000..98737288 --- /dev/null +++ b/apps/services/tools/importappgoods/main.go @@ -0,0 +1,296 @@ +// 一次性迁移工具:把各应用**业务库**(MySQL)的 goods 表(pb.DBGoods)拷贝到 +// console 主库(Postgres)的公共商品表 app_goods(comm.AppGoods),按 app_name 隔离。 +// +// · 业务库只读——绝不修改/删除应用本地 goods 表; +// · 目标表按 (app_name, goods_id) 判重:默认跳过已存在的商品(幂等,可重复执行), +// 加 -update 则用业务库数据覆盖已存在行(仅覆盖商品字段,不动 id/app_name/createtime); +// · -dry 只打印将要做什么,不写库。 +// +// 用法示例(DSN 也可用环境变量 MYSQL_DSN / PG_DSN 传,避免写进命令历史): +// +// go run ./tools/importappgoods -mysql '<业务库DSN>' -pg '' \ +// -apps 'earphone=Voitrans,lumi=Lumi,deapsound=Deapsound,deepglass=Deepglass,echomeet=Echomeet' -dry +// +// -apps 是「MySQL 库名=应用名」的映射,应用名须与 console 注册表 app_registry.app_name 一致, +// 否则后台按应用筛选时看不到这批商品。 +package main + +import ( + "flag" + "fmt" + "os" + "sort" + "strings" + "time" + + "yunyan/comm" + + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// srcGoods 业务库 goods 表的读取结构(字段取自 pb.DBGoods,只读用,故不引 pb 以免 gorm tag 干扰)。 +type srcGoods struct { + Id string `gorm:"column:id"` + Enable bool `gorm:"column:enable"` + Name string `gorm:"column:name"` + Localname string `gorm:"column:localname"` + Price int32 `gorm:"column:price"` + Usagetype int32 `gorm:"column:usagetype"` + Ainum int64 `gorm:"column:ainum"` + Meetnum int64 `gorm:"column:meetnum"` + Tradenum int64 `gorm:"column:tradenum"` + Viptime int32 `gorm:"column:viptime"` +} + +func main() { + var ( + mysqlDsn = flag.String("mysql", os.Getenv("MYSQL_DSN"), "业务库 MySQL DSN(库名部分会被 -apps 的库名逐个替换)") + pgDsn = flag.String("pg", os.Getenv("PG_DSN"), "console 主库 Postgres DSN") + appsArg = flag.String("apps", "", "MySQL库名=应用名,逗号分隔,如 earphone=Voitrans,lumi=Lumi") + dry = flag.Bool("dry", false, "只打印计划,不写库") + verify = flag.Bool("verify", false, "只读校验:逐条比对业务库与 app_goods 的字段是否一致") + update = flag.Bool("update", false, "已存在的商品用业务库数据覆盖(默认跳过)") + ) + flag.Parse() + + if *mysqlDsn == "" || *pgDsn == "" || *appsArg == "" { + fmt.Fprintln(os.Stderr, "缺少参数:-mysql / -pg / -apps 必填") + flag.Usage() + os.Exit(2) + } + + pairs, err := parseApps(*appsArg) + if err != nil { + fatal(err) + } + + pg, err := gorm.Open(postgres.Open(withExecQueryMode(*pgDsn)), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) + if err != nil { + fatal(fmt.Errorf("连接 console 主库失败: %w", err)) + } + if sqlDB, err := pg.DB(); err == nil { // 与 lego/sys/postgres 一样对 pooler 客气点 + sqlDB.SetMaxOpenConns(2) + sqlDB.SetMaxIdleConns(1) + } + // 表通常已由 console 启动时建好;只在确实缺表时建(不能直接 AutoMigrate—— + // 走 Supabase pooler 时它对已存在的表会报 42P07 relation already exists)。 + // 用 to_regclass 按 search_path 找表:gorm 的 HasTable 查的是 CURRENT_SCHEMA(), + // 经 Supabase pooler 连接时它与实际 search_path 不一致,会把已存在的表判成不存在。 + var tableExists bool + if err := pg.Raw("SELECT to_regclass(?) IS NOT NULL", comm.TableAppGoods).Scan(&tableExists).Error; err != nil { + fatal(fmt.Errorf("检查表 %s 是否存在失败: %w", comm.TableAppGoods, err)) + } + if !tableExists { + if err := pg.Table(comm.TableAppGoods).AutoMigrate(&comm.AppGoods{}); err != nil { + fatal(fmt.Errorf("建表 %s 失败: %w", comm.TableAppGoods, err)) + } + fmt.Printf("已创建表 %s\n", comm.TableAppGoods) + } + + // 注册表里的应用名,用于提醒拼写不一致(只提醒,不阻断) + var registered []string + if err := pg.Table("app_registry").Order("app_name").Pluck("app_name", ®istered).Error; err == nil { + fmt.Printf("console 注册表现有应用: %s\n", strings.Join(registered, ", ")) + } + + now := time.Now().Unix() + var totalIns, totalUpd, totalSkip int + + for _, p := range pairs { + dsn := replaceMySQLDB(*mysqlDsn, p.db) + src, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) + if err != nil { + fmt.Printf("✗ %-12s 连接业务库 %s 失败: %v\n", p.app, p.db, err) + continue + } + var rows []srcGoods + if err := src.Table(comm.TableGoods).Order("id").Find(&rows).Error; err != nil { + fmt.Printf("✗ %-12s 读取 %s.goods 失败: %v\n", p.app, p.db, err) + closeDB(src) + continue + } + closeDB(src) + + if !contains(registered, p.app) && len(registered) > 0 { + fmt.Printf("⚠ %-12s 不在 console 注册表 app_registry 中,后台按应用筛选可能看不到\n", p.app) + } + + var ins, upd, skip int + for i, r := range rows { + var exist comm.AppGoods + err := pg.Table(comm.TableAppGoods).Where("app_name=? AND goods_id=?", p.app, r.Id).Take(&exist).Error + if *verify { + switch { + case err == gorm.ErrRecordNotFound: + fmt.Printf("✗ %-12s %s 目标表缺失\n", p.app, r.Id) + ins++ + case err != nil: + fmt.Printf("✗ %-12s 查询 %s 失败: %v\n", p.app, r.Id, err) + default: + if d := diffGoods(&r, &exist); d != "" { + fmt.Printf("✗ %-12s %s 字段不一致: %s\n", p.app, r.Id, d) + upd++ + } else { + skip++ + } + } + continue + } + switch { + case err == gorm.ErrRecordNotFound: + m := &comm.AppGoods{ + AppName: p.app, GoodsId: r.Id, Enable: r.Enable, Name: r.Name, + Price: r.Price, Usagetype: r.Usagetype, Ainum: r.Ainum, Meetnum: r.Meetnum, + Tradenum: r.Tradenum, Viptime: r.Viptime, Localname: r.Localname, + Sort: int32((i + 1) * 10), Remark: "由业务库 " + p.db + ".goods 导入", + Createtime: now, Updatetime: now, + } + if !*dry { + if err := pg.Table(comm.TableAppGoods).Create(m).Error; err != nil { + fmt.Printf("✗ %-12s 写入 %s 失败: %v\n", p.app, r.Id, err) + continue + } + } + ins++ + case err != nil: + fmt.Printf("✗ %-12s 查询 %s 失败: %v\n", p.app, r.Id, err) + case *update: + exist.Enable, exist.Name, exist.Price, exist.Usagetype = r.Enable, r.Name, r.Price, r.Usagetype + exist.Ainum, exist.Meetnum, exist.Tradenum, exist.Viptime = r.Ainum, r.Meetnum, r.Tradenum, r.Viptime + exist.Localname, exist.Updatetime = r.Localname, now + if !*dry { + if err := pg.Table(comm.TableAppGoods).Save(&exist).Error; err != nil { + fmt.Printf("✗ %-12s 更新 %s 失败: %v\n", p.app, r.Id, err) + continue + } + } + upd++ + default: + skip++ + } + } + if *verify { + fmt.Printf("· %-12s(%s) 源 %2d 条 → 一致 %2d 不一致 %2d 缺失 %2d\n", p.app, p.db, len(rows), skip, upd, ins) + } else { + fmt.Printf("· %-12s(%s) 源 %2d 条 → 新增 %2d 更新 %2d 跳过(已存在) %2d\n", p.app, p.db, len(rows), ins, upd, skip) + } + totalIns, totalUpd, totalSkip = totalIns+ins, totalUpd+upd, totalSkip+skip + } + + switch { + case *verify: + fmt.Printf("\n校验完成:一致 %d,不一致 %d,缺失 %d\n", totalSkip, totalUpd, totalIns) + case *dry: + fmt.Printf("\nDRY-RUN(未写库):新增 %d,更新 %d,跳过 %d\n", totalIns, totalUpd, totalSkip) + default: + fmt.Printf("\n已写入:新增 %d,更新 %d,跳过 %d\n", totalIns, totalUpd, totalSkip) + } + + // 目标表现状 + type stat struct { + AppName string + N int64 + } + var stats []stat + if err := pg.Table(comm.TableAppGoods).Select("app_name, count(*) as n").Group("app_name").Order("app_name").Scan(&stats).Error; err == nil { + fmt.Println("app_goods 现有数据:") + for _, s := range stats { + fmt.Printf(" %-12s %d\n", s.AppName, s.N) + } + } +} + +// diffGoods 比对源(业务库)与目标(app_goods)的商品字段,返回不一致项的说明(空串=完全一致)。 +// 只比对「拷贝过来的」字段,app_goods 独有的 sort/remark/时间戳不参与。 +func diffGoods(s *srcGoods, d *comm.AppGoods) string { + var bad []string + cmp := func(name string, a, b any) { + if fmt.Sprint(a) != fmt.Sprint(b) { + bad = append(bad, fmt.Sprintf("%s(%v→%v)", name, a, b)) + } + } + cmp("enable", s.Enable, d.Enable) + cmp("name", s.Name, d.Name) + cmp("price", s.Price, d.Price) + cmp("usagetype", s.Usagetype, d.Usagetype) + cmp("ainum", s.Ainum, d.Ainum) + cmp("meetnum", s.Meetnum, d.Meetnum) + cmp("tradenum", s.Tradenum, d.Tradenum) + cmp("viptime", s.Viptime, d.Viptime) + if s.Localname != d.Localname { + bad = append(bad, "localname") + } + return strings.Join(bad, " ") +} + +// withExecQueryMode 与 lego/sys/postgres 保持一致:Supabase 的事务型 pooler(6543) +// 不保留会话级 prepared statement,pgx 默认的 cache_statement 会随机报 +// "prepared statement ... does not exist/already exists",必须改用 cache_describe。 +func withExecQueryMode(dsn string) string { + if strings.Contains(dsn, "default_query_exec_mode") { + return dsn + } + sep := "?" + if strings.Contains(dsn, "?") { + sep = "&" + } + return dsn + sep + "default_query_exec_mode=cache_describe" +} + +type appPair struct{ db, app string } + +func parseApps(s string) (out []appPair, err error) { + for _, seg := range strings.Split(s, ",") { + seg = strings.TrimSpace(seg) + if seg == "" { + continue + } + db, app, ok := strings.Cut(seg, "=") + if !ok || strings.TrimSpace(db) == "" || strings.TrimSpace(app) == "" { + return nil, fmt.Errorf("-apps 片段格式应为 库名=应用名,收到: %q", seg) + } + out = append(out, appPair{strings.TrimSpace(db), strings.TrimSpace(app)}) + } + if len(out) == 0 { + return nil, fmt.Errorf("-apps 为空") + } + sort.Slice(out, func(i, j int) bool { return out[i].app < out[j].app }) + return out, nil +} + +// replaceMySQLDB 把 DSN 里 ")/" 之后、"?" 之前的库名换成目标库(同一实例多库共用一份账号)。 +func replaceMySQLDB(dsn, db string) string { + i := strings.LastIndex(dsn, ")/") + if i < 0 { + return dsn + } + head := dsn[:i+2] + rest := dsn[i+2:] + if j := strings.Index(rest, "?"); j >= 0 { + return head + db + rest[j:] + } + return head + db +} + +func contains(list []string, s string) bool { + for _, v := range list { + if v == s { + return true + } + } + return false +} + +func closeDB(db *gorm.DB) { + if sqlDB, err := db.DB(); err == nil { + _ = sqlDB.Close() + } +} + +func fatal(err error) { + fmt.Fprintln(os.Stderr, "✗", err) + os.Exit(1) +} diff --git a/deploy/admin/prod-deploy.sh b/deploy/admin/prod-deploy.sh index cbf5847e..1094056a 100755 --- a/deploy/admin/prod-deploy.sh +++ b/deploy/admin/prod-deploy.sh @@ -46,7 +46,7 @@ env_profile() { ENV_DESC="云雁生产环境 · 上海阿里云" registry_profile yunyan || return 1 # 镜像仓库:云雁 DEPLOY_HOST="139.224.212.237" # ← 云雁生产服务器 IP(上海) - DEPLOY_KEY=/Users/liwei/Documents/key/aliyun_shanghai.pem + DEPLOY_KEY="/Users/liwei1dao/work/密钥/release_shanghai.pem" DEPLOY_DIR="/home/work/starpivot/admin" # 远端部署目录(其中 docker-compose.yml/.env 由你自己维护) ;; lingpu) diff --git a/deploy/app/prod-deploy.sh b/deploy/app/prod-deploy.sh index 9204f210..174b455c 100755 --- a/deploy/app/prod-deploy.sh +++ b/deploy/app/prod-deploy.sh @@ -88,7 +88,7 @@ region_profile() { # 远端部署目录/容器名 DEPLOY_HOST="139.224.212.237" # ← 上海生产服务器 IP(必填) DEPLOY_PORT=22 DEPLOY_USER=root - DEPLOY_KEY=/Users/liwei/Documents/key/aliyun_shanghai.pem # ← 上海服务器 SSH 私钥(各区域不同) + DEPLOY_KEY="/Users/liwei1dao/work/密钥/release_shanghai.pem" # ← 上海服务器 SSH 私钥(各区域不同) ;; japan) DEPLOY_HOST="8.216.34.101" # ← 日本生产服务器 IP(必填) diff --git a/deploy/console/prod-deploy.sh b/deploy/console/prod-deploy.sh index 38e7b244..e717c155 100755 --- a/deploy/console/prod-deploy.sh +++ b/deploy/console/prod-deploy.sh @@ -46,7 +46,7 @@ env_profile() { ENV_DESC="云雁生产环境 · 上海阿里云" registry_profile yunyan || return 1 # 镜像仓库:云雁 DEPLOY_HOST="139.224.212.237" # ← 云雁生产服务器 IP(上海) - DEPLOY_KEY=/Users/liwei/Documents/key/aliyun_shanghai.pem + DEPLOY_KEY="/Users/liwei1dao/work/密钥/release_shanghai.pem" DEPLOY_DIR="/home/work/starpivot/console" # 远端部署目录(其中 docker-compose.yml/.env/confs 由你自己维护) ;; lingpu)