Browse Source

上传代码

main
liwei1dao 2 months ago
parent
commit
aca7204e57
  1. 11
      CLAUDE.md
  2. 13
      apps/admin/app/components/AppModuleTab.vue
  3. 48
      apps/services/comm/moduleconfig.go
  4. 68
      apps/services/sys/sms/aliyun.go
  5. 89
      apps/services/sys/sms/dispatch_test.go
  6. 103
      apps/services/sys/sms/options.go
  7. 87
      apps/services/sys/sms/sms.go
  8. 72
      apps/services/sys/sms/tencent.go
  9. 4
      deploy/admin/README.md
  10. 2
      deploy/admin/prod-build.sh
  11. 11
      deploy/admin/prod-deploy.sh
  12. 6
      deploy/app/README.md
  13. 10
      deploy/app/confs/home.yaml.example
  14. 4
      deploy/app/prod-build.sh
  15. 35
      deploy/app/prod-deploy.sh
  16. 4
      deploy/console/README.md
  17. 2
      deploy/console/prod-build.sh
  18. 11
      deploy/console/prod-deploy.sh
  19. 9
      deploy/registry-profiles.sh

11
CLAUDE.md

@ -64,20 +64,20 @@ cd deploy/console && ./dev-deploy.sh along # 阿龙测试环境(阿里
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-build.sh 0.2.1 yunyan # 第 2 个参数选公司(yunyan|lingpu|along),省略则交互选
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`(灵谱香港机)
- `along` → `registry.ymaikj.com`(仓库实体就在阿龙机 8.133.166.29 上,**账号是 `admin` 不是 `liwei`**);只用于测试环境,不在生产 `COMPANIES` 菜单里
- `along` → `registry.ymaikj.com`(仓库实体就在阿龙测试机 8.133.166.29 上,**账号是 `admin` 不是 `liwei`**);阿龙测试机与正式机 47.116.104.181 都从这里拉镜像
测试环境档案在各 `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 三个服务、两家公司都一样),映射在
**生产镜像名统一为 `starpivot-<服务>`**(admin/console/app 三个服务、各家公司都一样),映射在
`registry-profiles.sh` 的 `prod_image_name()`——build 与 deploy 共用这一份,别在各脚本里写死。
compose 的 image 由脚本注入 `IMAGE_NAME` 变量,同样不写死。
@ -92,6 +92,11 @@ admin/console 的 `prod-deploy.sh` 已改为**多生产环境档案**(`env_pro
- `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`
- `along` → 47.116.104.181(阿龙正式机,2026-08-16 补),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/.ssh/along.pem`(与测试机同一把);镜像走阿龙仓库 `registry.ymaikj.com`
app 的 `prod-deploy.sh` 是**按区域**(`region_profile()`):`shanghai|japan|singapore` 是云雁三地,新增 `along` → 47.116.104.181。
该函数除服务器连接信息外还给两项:`REGION_COMPANY`(这个区域拉哪家仓库,云雁三地=yunyan、阿龙=along;可用环境变量 `COMPANY` 覆盖)、
`DEPLOY_DIR_OVERRIDE`(阿龙用 `/home/work/starpivot/app`,不走 `APPS` 里的 `/home/work/<应用>-a11`)。
`dev-deploy.sh <环境>` 只做镜像部署:构建推送 → 远端 pull 重启,**不上传** 服务器上的真实 `.env` / `confs/*.yaml`(只下发 `*.example` 模板)。

13
apps/admin/app/components/AppModuleTab.vue

@ -62,7 +62,7 @@
</div>
<div class="grid grid-cols-1 gap-3 max-w-2xl">
<div v-for="f in curDef.fields" :key="f.key" class="form-control">
<div v-for="f in visibleFields" :key="f.key" class="form-control">
<label class="label py-0.5">
<span class="label-text text-sm">
{{ f.label }}
@ -122,9 +122,10 @@ const { consoleApi } = useApi()
const { success, info, error } = useToast() as any
interface FieldOption { value: string; label: string }
interface FieldCond { key: string; values: string[] }
interface ModuleField {
key: string; label: string; type: string; secret: boolean; required: boolean
placeholder?: string; desc?: string; options?: FieldOption[]
placeholder?: string; desc?: string; options?: FieldOption[]; show_if?: FieldCond
}
interface ModuleDef { module: string; name: string; group: string; sys_key: string; desc?: string; fields: ModuleField[] }
interface ModItem { module: string; key: string; value: string; encrypted: boolean }
@ -142,6 +143,14 @@ const dbError = ref('')
const curDef = computed(() => catalog.value.find((m) => m.module === curModule.value) || null)
const cur = computed(() => values[curModule.value] || {})
// 按 show_if 过滤字段:如短信模块只显示当前所选服务商(Provider)的凭据字段。
// 只影响显示,保存时仍提交全量字段(见 save),所以另一家的凭据不会被隐藏字段清掉。
const visibleFields = computed(() => {
const def = curDef.value
if (!def) return [] as ModuleField[]
return def.fields.filter((f) => !f.show_if || f.show_if.values.includes(cur.value[f.show_if.key] ?? ''))
})
const groups = computed(() => {
const order: string[] = []
const map = new Map<string, ModuleDef[]>()

48
apps/services/comm/moduleconfig.go

@ -39,6 +39,14 @@ type ModuleFieldOption struct {
Label string `json:"label"`
}
// ModuleFieldCond 字段的显示条件:同模块内 Key 字段的当前值命中 Values 之一时才显示。
// 用于「一个模块多服务商」的场景(如短信按 Provider 只显示对应厂商的字段),
// 免得把两家凭据一股脑铺在同一张表单上。空值也要显示时,Values 里带上 ""。
type ModuleFieldCond struct {
Key string `json:"key"`
Values []string `json:"values"`
}
// ModuleField 一个模块的一个配置字段的元数据(驱动前端表单 + 后端密钥判定)。
type ModuleField struct {
Key string `json:"key"`
@ -49,6 +57,7 @@ type ModuleField struct {
Placeholder string `json:"placeholder,omitempty"`
Desc string `json:"desc,omitempty"`
Options []ModuleFieldOption `json:"options,omitempty"`
ShowIf *ModuleFieldCond `json:"show_if,omitempty"` // 仅影响前端显示;后端仍按全量字段存取
}
// ModuleDef 一个业务模块(如"微信登录")的定义。
@ -88,14 +97,39 @@ var ModuleCatalog = []ModuleDef{
},
{
Module: "sms", Name: "短信服务", Group: ModuleGroupNotify, SysKey: "sms",
Desc: "腾讯云短信",
Desc: "验证码短信,支持腾讯云 / 阿里云;切换服务商后凭据各存各的,签名与模板需按所选服务商填写",
Fields: []ModuleField{
{Key: "AppId", Label: "短信 SDK AppID", Type: "text", Required: true},
{Key: "SecretId", Label: "SecretId", Type: "text", Required: true},
{Key: "SecretKey", Label: "SecretKey", Type: "password", Secret: true, Required: true},
{Key: "SignName", Label: "短信签名", Type: "text", Required: true},
{Key: "Template1", Label: "国内模板 ID", Type: "text"},
{Key: "Template2", Label: "国际模板 ID", Type: "text"},
{Key: "Provider", Label: "服务商", Type: "select", Required: true, Options: []ModuleFieldOption{
{Value: "tencent", Label: "腾讯云"}, {Value: "aliyun", Label: "阿里云"},
}, Desc: "留空按腾讯云处理(兼容旧配置)"},
// 腾讯云
{Key: "AppId", Label: "短信 SDK AppID", Type: "text", Required: true,
ShowIf: &ModuleFieldCond{Key: "Provider", Values: []string{"", "tencent"}}},
{Key: "SecretId", Label: "SecretId", Type: "text", Required: true,
ShowIf: &ModuleFieldCond{Key: "Provider", Values: []string{"", "tencent"}}},
{Key: "SecretKey", Label: "SecretKey", Type: "password", Secret: true, Required: true,
ShowIf: &ModuleFieldCond{Key: "Provider", Values: []string{"", "tencent"}}},
// 阿里云
{Key: "AccessKeyId", Label: "AccessKeyId", Type: "text", Required: true,
ShowIf: &ModuleFieldCond{Key: "Provider", Values: []string{"aliyun"}}},
{Key: "AccessKeySecret", Label: "AccessKeySecret", Type: "password", Secret: true, Required: true,
ShowIf: &ModuleFieldCond{Key: "Provider", Values: []string{"aliyun"}}},
{Key: "RegionId", Label: "地域", Type: "text", Placeholder: "cn-hangzhou",
Desc: "留空默认 cn-hangzhou;国际站账号按需填 ap-southeast-1",
ShowIf: &ModuleFieldCond{Key: "Provider", Values: []string{"aliyun"}}},
{Key: "TemplateParamName", Label: "模板变量名", Type: "text", Placeholder: "code",
Desc: "验证码填进模板的哪个变量,对应模板里的 ${code};留空默认 code",
ShowIf: &ModuleFieldCond{Key: "Provider", Values: []string{"aliyun"}}},
// 两家共用
{Key: "SignName", Label: "短信签名", Type: "text", Required: true,
Desc: "须是所选服务商后台已审核通过的签名"},
{Key: "Template1", Label: "国内模板", Type: "text",
Desc: "腾讯云填 TemplateId / 阿里云填 TemplateCode(如 SMS_123456789)"},
{Key: "Template2", Label: "国际模板", Type: "text",
Desc: "港澳台及海外号码用;只做国内可留空"},
},
},
{

68
apps/services/sys/sms/aliyun.go

@ -0,0 +1,68 @@
package sms
import (
"encoding/json"
"fmt"
"strings"
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
)
func newAliyun(options Options) (sys *Aliyun, err error) {
sys = &Aliyun{options: options}
sys.client, err = dysmsapi.NewClientWithAccessKey(options.RegionId, options.AccessKeyId, options.AccessKeySecret)
return
}
type Aliyun struct {
options Options
client *dysmsapi.Client
}
// SendCaptcha 发送验证码短信(阿里云 Dysmsapi SendSms)。
func (this *Aliyun) SendCaptcha(mobile string, captcha string) (err error) {
template, err := pickTemplate(this.options, mobile)
if err != nil {
return
}
param, err := json.Marshal(map[string]string{this.options.TemplateParamName: captcha})
if err != nil {
return
}
request := dysmsapi.CreateSendSmsRequest()
request.Scheme = "https"
request.PhoneNumbers = aliMobile(mobile)
request.SignName = this.options.SignName
request.TemplateCode = template
request.TemplateParam = string(param)
resp, err := this.client.SendSms(request)
if err != nil {
return
}
// 阿里云同样是「HTTP 成功 ≠ 发送成功」:真实结果看 Code,成功为 "OK"。
if resp == nil {
return fmt.Errorf("阿里云短信发送失败: 返回为空")
}
if resp.Code != "OK" {
return fmt.Errorf("阿里云短信发送失败: %s %s", resp.Code, resp.Message)
}
return
}
// aliMobile 把号码转成阿里云要求的格式:
// 国内号只要 11 位裸号(带 +86/0086 前缀会被判为非法号码),
// 国际/港澳台号用 "00+国际区号+号码"(阿里云不接受 "+" 开头)。
func aliMobile(mobile string) string {
m := normalizeMobile(mobile)
if isDomestic(m) {
m = strings.TrimPrefix(m, "+86")
m = strings.TrimPrefix(m, "0086")
return m
}
if strings.HasPrefix(m, "+") {
return "00" + strings.TrimPrefix(m, "+")
}
return m
}

89
apps/services/sys/sms/dispatch_test.go

@ -0,0 +1,89 @@
package sms
import "testing"
// 纯本地逻辑测试(不发网络):号码归属判定、阿里云号码格式、模板选取、配置校验。
func TestIsDomestic(t *testing.T) {
cases := map[string]bool{
"+8615336758045": true,
"008615336758045": true,
"15336758045": true,
"+852 7027 8175": false,
"+85270278175": false,
"0085270278175": false,
"+14155552671": false,
}
for mobile, want := range cases {
if got := isDomestic(mobile); got != want {
t.Errorf("isDomestic(%q)=%v, want %v", mobile, got, want)
}
}
}
func TestAliMobile(t *testing.T) {
cases := map[string]string{
"+8615336758045": "15336758045",
"008615336758045": "15336758045",
"153-3675-8045": "15336758045",
"+85270278175": "0085270278175",
"0085270278175": "0085270278175",
}
for in, want := range cases {
if got := aliMobile(in); got != want {
t.Errorf("aliMobile(%q)=%q, want %q", in, got, want)
}
}
}
func TestPickTemplate(t *testing.T) {
o := Options{Template1: "domestic", Template2: "oversea"}
if got, _ := pickTemplate(o, "+8615336758045"); got != "domestic" {
t.Errorf("国内号应选 Template1,得到 %q", got)
}
if got, _ := pickTemplate(o, "+85270278175"); got != "oversea" {
t.Errorf("国际号应选 Template2,得到 %q", got)
}
// 只配了国内模板时,国际号回退到国内模板而不是发一条没有模板的空请求。
only := Options{Template1: "domestic"}
if got, _ := pickTemplate(only, "+85270278175"); got != "domestic" {
t.Errorf("缺国际模板应回退 Template1,得到 %q", got)
}
if _, err := pickTemplate(Options{}, "15336758045"); err == nil {
t.Error("两个模板都没配时应报错")
}
}
func TestOptionsValidateAndDefaults(t *testing.T) {
// Provider 留空 = 腾讯云(兼容加入阿里云之前的配置)。
o, err := newOptionsChecked(map[string]interface{}{"SecretId": "id", "SecretKey": "key"})
if err != nil {
t.Fatalf("腾讯云旧配置应通过校验: %v", err)
}
if o.Provider != ProviderTencent {
t.Errorf("Provider 留空应归一为 %s,得到 %q", ProviderTencent, o.Provider)
}
// 阿里云:缺 AccessKey 应报错,而不是等到发送时才失败。
if _, err := newOptionsChecked(map[string]interface{}{"Provider": "aliyun", "SignName": "签名"}); err == nil {
t.Error("阿里云缺 AccessKeyId 应校验失败")
}
// 阿里云:地域与模板变量名留空时填默认值。
o, err = newOptionsChecked(map[string]interface{}{
"Provider": "Aliyun", "AccessKeyId": "ak", "AccessKeySecret": "sk", "SignName": "签名",
})
if err != nil {
t.Fatalf("阿里云配置应通过校验: %v", err)
}
if o.Provider != ProviderAliyun {
t.Errorf("Provider 应忽略大小写归一为 %s,得到 %q", ProviderAliyun, o.Provider)
}
if o.RegionId != defaultAliRegionId || o.TemplateParamName != defaultAliTemplateParam {
t.Errorf("阿里云默认值未填充: RegionId=%q TemplateParamName=%q", o.RegionId, o.TemplateParamName)
}
if _, err := newOptionsChecked(map[string]interface{}{"Provider": "huawei"}); err == nil {
t.Error("未知服务商应校验失败")
}
}

103
apps/services/sys/sms/options.go

@ -2,18 +2,49 @@ package sms
import (
"errors"
"fmt"
"strings"
"yunyan/lego/utils/mapstructure"
)
// 短信服务商。Provider 留空按腾讯云处理(兼容加入阿里云之前已落库/落 yaml 的配置)。
const (
ProviderTencent = "tencent"
ProviderAliyun = "aliyun"
)
// 阿里云默认值:地域与模板参数名。
const (
defaultAliRegionId = "cn-hangzhou"
defaultAliTemplateParam = "code"
)
type Option func(*Options)
type Options struct {
Provider string // tencent(默认) | aliyun
// 腾讯云
AppId string
SecretId string
SecretKey string
// 阿里云
AccessKeyId string
AccessKeySecret string
RegionId string // 默认 cn-hangzhou;国际站按需 ap-southeast-1
TemplateParamName string // 模板变量名,默认 code(对应模板里的 ${code})
// 两家共用
SignName string
Template1 string
Template2 string
Template1 string // 国内模板(腾讯 TemplateId / 阿里 TemplateCode)
Template2 string // 国际/港澳台模板
}
func SetProvider(v string) Option {
return func(o *Options) {
o.Provider = v
}
}
func SetAppId(v string) Option {
@ -34,6 +65,30 @@ func KeySecretKey(v string) Option {
}
}
func SetAccessKeyId(v string) Option {
return func(o *Options) {
o.AccessKeyId = v
}
}
func SetAccessKeySecret(v string) Option {
return func(o *Options) {
o.AccessKeySecret = v
}
}
func SetRegionId(v string) Option {
return func(o *Options) {
o.RegionId = v
}
}
func SetTemplateParamName(v string) Option {
return func(o *Options) {
o.TemplateParamName = v
}
}
func SetSignName(v string) Option {
return func(o *Options) {
o.SignName = v
@ -54,16 +109,53 @@ func newOptionsByOption(opts ...Option) (Options, error) {
for _, o := range opts {
o(&options)
}
options.normalize()
return options, options.validate()
}
// validate 校验发短信必需字段。
// provider 归一化后的服务商(空 → 腾讯云)。
func (o Options) provider() string {
if p := strings.ToLower(strings.TrimSpace(o.Provider)); p != "" {
return p
}
return ProviderTencent
}
// normalize 填充可留空字段的默认值,并把服务商归一化成小写。
func (o *Options) normalize() {
o.Provider = o.provider()
if o.Provider == ProviderAliyun {
if o.RegionId == "" {
o.RegionId = defaultAliRegionId
}
if o.TemplateParamName == "" {
o.TemplateParamName = defaultAliTemplateParam
}
}
}
// validate 校验发短信必需字段,按服务商分别校验。
func (o Options) validate() error {
switch o.provider() {
case ProviderTencent:
if o.SecretId == "" {
return errors.New("sms 配置缺失: SecretId 为空")
return errors.New("sms 配置缺失: SecretId 为空(腾讯云)")
}
if o.SecretKey == "" {
return errors.New("sms 配置缺失: SecretKey 为空")
return errors.New("sms 配置缺失: SecretKey 为空(腾讯云)")
}
case ProviderAliyun:
if o.AccessKeyId == "" {
return errors.New("sms 配置缺失: AccessKeyId 为空(阿里云)")
}
if o.AccessKeySecret == "" {
return errors.New("sms 配置缺失: AccessKeySecret 为空(阿里云)")
}
if o.SignName == "" {
return errors.New("sms 配置缺失: SignName 为空(阿里云签名必填)")
}
default:
return fmt.Errorf("sms 配置错误: 未知服务商 Provider=%q(可选 %s / %s)", o.Provider, ProviderTencent, ProviderAliyun)
}
return nil
}
@ -78,5 +170,6 @@ func newOptionsChecked(config map[string]interface{}, opts ...Option) (Options,
for _, o := range opts {
o(&options)
}
options.normalize()
return options, options.validate()
}

87
apps/services/sys/sms/sms.go

@ -1,58 +1,55 @@
package sms
import (
"fmt"
"strings"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" // 引入sms
)
func newSys(options Options) (sys *SMS, err error) {
sys = &SMS{options: options}
// 必要步骤:
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
credential := common.NewCredential(
options.SecretId, // 替换为你的SecretId
options.SecretKey, // 替换为你的SecretKey
)
// 实例化一个客户端配置对象,可以指定超时时间等配置
cpf := profile.NewClientProfile()
cpf.HttpProfile.ReqMethod = "POST"
cpf.HttpProfile.ReqTimeout = 30
cpf.SignMethod = "HmacSHA1"
// 实例化要请求产品的client对象
sys.client, err = sms.NewClient(credential, "ap-guangzhou", cpf)
return
// newSys 按 Provider 建对应服务商的客户端(腾讯云 / 阿里云)。
func newSys(options Options) (sys ISys, err error) {
switch options.provider() {
case ProviderTencent:
return newTencent(options)
case ProviderAliyun:
return newAliyun(options)
default:
return nil, fmt.Errorf("sms 未知服务商: %s", options.Provider)
}
}
type SMS struct {
options Options
client *sms.Client
// isDomestic 判断是否国内号码(决定用国内模板还是国际模板)。
// 号码来自客户端,格式不统一:可能是 +8613…、008613…、13… 或国际号 +852…。
// 带国家码前缀的看前缀;不带前缀的只有"1 开头的 11 位"按国内算(大陆手机号),
// 其余仍按国际处理——与加入阿里云之前的行为保持一致。
func isDomestic(mobile string) bool {
m := normalizeMobile(mobile)
if strings.HasPrefix(m, "+") {
return strings.HasPrefix(m, "+86")
}
if strings.HasPrefix(m, "00") {
return strings.HasPrefix(m, "0086")
}
return len(m) == 11 && strings.HasPrefix(m, "1")
}
// 发送短信验证吗
func (this *SMS) SendCaptcha(mobile string, captcha string) (err error) {
var (
Template string
)
if strings.Contains(mobile, "+86") {
Template = this.options.Template1
} else {
Template = this.options.Template2
// normalizeMobile 去掉号码里的空格、短横等分隔符。
func normalizeMobile(mobile string) string {
return strings.NewReplacer(" ", "", "-", "", "\t", "").Replace(strings.TrimSpace(mobile))
}
// 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
request := sms.NewSendSmsRequest()
// 基本类型的设置。
request.SmsSdkAppId = common.StringPtr(this.options.AppId) // 替换为你的SDK AppID
request.SignName = common.StringPtr(this.options.SignName) // 替换为你的签名
request.TemplateId = common.StringPtr(Template) // 替换为你的模板ID
request.PhoneNumberSet = common.StringPtrs([]string{mobile}) // 替换为接收短信的手机号
request.TemplateParamSet = common.StringPtrs([]string{captcha}) // 替换为模板中的参数
// 通过client对象调用想要访问的接口,需要传入请求对象
if _, err = this.client.SendSms(request); err != nil {
return
}
return
// pickTemplate 按号码归属选模板;对应模板没配时回退到另一个,两个都没配则报错
// (模板 ID 是发短信的必填项,缺了发出去也是失败,不如早点给出可读的错误)。
func pickTemplate(o Options, mobile string) (string, error) {
domestic := isDomestic(mobile)
primary, fallback := o.Template1, o.Template2
if !domestic {
primary, fallback = o.Template2, o.Template1
}
if primary != "" {
return primary, nil
}
if fallback != "" {
return fallback, nil
}
return "", fmt.Errorf("sms 配置缺失: 未配置短信模板(Template1/Template2)")
}

72
apps/services/sys/sms/tencent.go

@ -0,0 +1,72 @@
package sms
import (
"fmt"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" // 引入sms
)
func newTencent(options Options) (sys *Tencent, err error) {
sys = &Tencent{options: options}
// 必要步骤:
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。
credential := common.NewCredential(
options.SecretId, // 替换为你的SecretId
options.SecretKey, // 替换为你的SecretKey
)
// 实例化一个客户端配置对象,可以指定超时时间等配置
cpf := profile.NewClientProfile()
cpf.HttpProfile.ReqMethod = "POST"
cpf.HttpProfile.ReqTimeout = 30
cpf.SignMethod = "HmacSHA1"
// 实例化要请求产品的client对象
sys.client, err = sms.NewClient(credential, "ap-guangzhou", cpf)
return
}
type Tencent struct {
options Options
client *sms.Client
}
// 发送短信验证吗
func (this *Tencent) SendCaptcha(mobile string, captcha string) (err error) {
template, err := pickTemplate(this.options, mobile)
if err != nil {
return
}
// 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
request := sms.NewSendSmsRequest()
// 基本类型的设置。
request.SmsSdkAppId = common.StringPtr(this.options.AppId) // 替换为你的SDK AppID
request.SignName = common.StringPtr(this.options.SignName) // 替换为你的签名
request.TemplateId = common.StringPtr(template) // 替换为你的模板ID
request.PhoneNumberSet = common.StringPtrs([]string{mobile}) // 替换为接收短信的手机号
request.TemplateParamSet = common.StringPtrs([]string{captcha}) // 替换为模板中的参数
// 通过client对象调用想要访问的接口,需要传入请求对象
resp, err := this.client.SendSms(request)
if err != nil {
return
}
// 接口调用成功 ≠ 短信发出去了:签名/模板/号码有问题时 err 为 nil,
// 真实结果在 SendStatusSet[i].Code(成功为 "Ok")。不看它会把失败当成功返回给客户端。
if resp == nil || resp.Response == nil || len(resp.Response.SendStatusSet) == 0 {
return fmt.Errorf("腾讯云短信发送失败: 返回为空")
}
for _, st := range resp.Response.SendStatusSet {
if st == nil || st.Code == nil {
continue
}
if *st.Code != "Ok" {
msg := ""
if st.Message != nil {
msg = *st.Message
}
return fmt.Errorf("腾讯云短信发送失败: %s %s", *st.Code, msg)
}
}
return
}

4
deploy/admin/README.md

@ -49,7 +49,7 @@ cd deploy/admin
./dev-deploy.sh yunyan # 开发:发布到云雁测试环境
./dev-deploy.sh # 不带参数 → 交互选择测试环境
./prod-build.sh 0.1.1 yunyan # 生产①:构建并推送 starpivot-admin:0.1.1 (+latest);第2参数选公司
./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu),省略环境则交互选
./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu|along),省略环境则交互选
./prod-deploy.sh rollback lingpu # 生产回滚到上一版本(与当前版本来回切换)
```
@ -58,7 +58,7 @@ cd deploy/admin
## 需要填写 / 提供的东西
- **部署目标与凭据**:写死在各脚本顶部。`dev-deploy.sh` 的 `env_profile()`(每个测试环境一套 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`,现有 `lingpu`、`yunyan`;新增环境加一段 case 并写进 `ENVS`)、`prod-deploy.sh` 的 `env_profile()`(每个生产环境一套 `REGISTRY/IMAGE_NAME/DEPLOY_HOST/DEPLOY_KEY/DEPLOY_DIR`,现有 `yunyan`、`lingpu`;新增环境加一段 case 并写进 `PROD_ENVS`);
- **部署目标与凭据**:写死在各脚本顶部。`dev-deploy.sh` 的 `env_profile()`(每个测试环境一套 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`,现有 `lingpu`、`yunyan`、`along`;新增环境加一段 case 并写进 `ENVS`)、`prod-deploy.sh` 的 `env_profile()`(每个生产环境一套 `REGISTRY/IMAGE_NAME/DEPLOY_HOST/DEPLOY_KEY/DEPLOY_DIR`,现有 `yunyan`、`lingpu`、`along`;新增环境加一段 case 并写进 `PROD_ENVS`);
仓库账号 `admin` / 密码已写死;`DEPLOY_KEY` 默认 `/Users/liwei/Documents/key/yunyan-us.pem`(prod)。
- ⚠️ 本目录脚本纳入 git,**公开仓库前务必清空** `*_HOST` 与 `REGISTRY_PASS`。
- **后端地址**:`.env` 的 `CONSOLE_BACKEND`,须指向同网络的 console 容器,默认 `http://starpivot-console:8080`。

2
deploy/admin/prod-build.sh

@ -6,7 +6,7 @@
#
# 用法(在 deploy/admin/ 下):
# ./prod-build.sh <version> # version 必须形如 X.Y.Z,如 0.1.1
# ./prod-build.sh <version> [公司] # 公司 = yunyan | lingpu(省略则交互选择;无 TTY 用默认 yunyan)
# ./prod-build.sh <version> [公司] # 公司 = yunyan | lingpu | along(省略则交互选择;无 TTY 用默认 yunyan)
set -euo pipefail
SVC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

11
deploy/admin/prod-deploy.sh

@ -8,7 +8,7 @@
# 用法(在 deploy/admin/ 下):
# ./prod-deploy.sh <tag> [环境] # 部署指定版本,如 ./prod-deploy.sh 0.1.1 lingpu
# ./prod-deploy.sh rollback [环境] # 回滚到上一次部署版本(与当前版本来回切换)
# 环境省略 → 交互选择;无 TTY 时用默认环境 yunyan。
# 环境 = yunyan(上海)| lingpu(广州)| along(阿龙 47.116.104.181);省略 → 交互选择,无 TTY 时用默认 yunyan。
# ⚠️ 环境必须与 prod-build.sh 构建时所选公司一致,否则拉不到镜像。
set -euo pipefail
@ -32,7 +32,7 @@ DOCKER_NETWORK="1panel-network" # compose 外部网络名(与服务
# 容器名:必须与 docker-compose.yml 的 container_name 一字不差(本脚本会上传覆盖那份 compose)。
# 仅用于 up 冲突时清理同名遗留容器;不随环境变化。
CONTAINER_NAME="starpivot-admin"
PROD_ENVS=(yunyan lingpu) # 可选生产环境(交互菜单按此顺序展示)
PROD_ENVS=(yunyan lingpu along) # 可选生产环境(交互菜单按此顺序展示)
DEFAULT_PROD_ENV="yunyan" # 无参数且无 TTY(CI/管道)时的默认环境
env_profile() {
@ -56,6 +56,13 @@ env_profile() {
DEPLOY_KEY=~/.ssh/lingpu-gz.pem # 私钥放 ~/.ssh:~/Documents 会被 macOS TCC 挡住读不出来
DEPLOY_DIR="/home/work/starpivot/admin"
;;
along)
ENV_DESC="阿龙生产环境 · 阿里云"
registry_profile along || return 1 # 镜像仓库:registry.ymaikj.com(实体在阿龙测试机 8.133.166.29,账号 admin)
DEPLOY_HOST="47.116.104.181" # ← 阿龙生产服务器 IP
DEPLOY_KEY=~/.ssh/along.pem # 与测试机同一把钥匙;原件在 ~/Documents/阿龙/loginscre.pem,因 macOS TCC 挡文稿目录已拷贝到 ~/.ssh
DEPLOY_DIR="/home/work/starpivot/admin"
;;
*) red "✗ 未知环境: ${DEPLOY_ENV}(应为 ${PROD_ENVS[*]// /|})"; exit 1 ;;
esac
IMAGE_NAME="$(prod_image_name "$DEPLOY_ENV" admin)" || return 1

6
deploy/app/README.md

@ -56,6 +56,7 @@ cd deploy/voitrans
./prod-deploy.sh shanghai 0.1.1 # 部署 0.1.1 到上海
./prod-deploy.sh japan 0.1.1 # 部署到日本
./prod-deploy.sh singapore 0.1.1 # 部署到新加坡
./prod-deploy.sh voitrans along 0.1.1 # 部署到阿龙正式机(镜像走阿龙仓库,需先 ./prod-build.sh 0.1.1 along)
./prod-deploy.sh shanghai rollback # 上海回滚到上一版本(与当前版本来回切换)
```
@ -65,8 +66,9 @@ cd deploy/voitrans
## 需要填写 / 提供的东西
- **部署目标与凭据**:写死在各脚本顶部。
- `dev-deploy.sh`:`env_profile()` 里各测试环境(`lingpu` / `yunyan`)的 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`。
- `prod-deploy.sh`:`region_profile()` 里各区域的 `DEPLOY_HOST`。
- `dev-deploy.sh`:`env_profile()` 里各测试环境(`lingpu` / `yunyan` / `along`)的 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`。
- `prod-deploy.sh`:`region_profile()` 里各区域(`shanghai` / `japan` / `singapore` / `along`)的 `DEPLOY_HOST`;
另有 `REGION_COMPANY`(该区域拉哪个公司的仓库,可用环境变量 `COMPANY` 覆盖)与 `DEPLOY_DIR_OVERRIDE`(区域目录规范与 `APPS` 不同时用)。
- 仓库账号 `admin` / 密码已写死;`DEPLOY_KEY` 默认 `~/.ssh/id_rsa`。
- ⚠️ 本目录脚本纳入 git,**公开仓库前务必清空** `*_HOST` 与 `REGISTRY_PASS`。
- **基础设施端点**:填服务器 `.env` 的 `ETCD_SERVERS / DB_ADMIN_DSN / DB_DEVICE_DSN / REDIS_* / NATS_URL`,

10
deploy/app/confs/home.yaml.example

@ -49,10 +49,18 @@ sys:
Password: ${REDIS_PASSWORD:-}
DB: ${REDIS_DB:-1}
KeyPrefix: ${REDIS_KEY_PREFIX:-Voitrans} #key 应用分组前缀
sms: #短信服务
sms: #短信服务(服务商 tencent | aliyun,留空按 tencent;也可在 console 后台「业务功能配置」里改,改完热生效)
Provider: "tencent"
#腾讯云
AppId: "1400971698"
SecretId: ""
SecretKey: ""
#阿里云(Provider=aliyun 时用;RegionId 留空默认 cn-hangzhou,TemplateParamName 留空默认 code)
AccessKeyId: ""
AccessKeySecret: ""
RegionId: ""
TemplateParamName: ""
#两家共用:签名与模板须按所选服务商填(腾讯 TemplateId / 阿里 TemplateCode)
SignName: "深圳市创想云启"
Template1: "2397800"
Template2: "2385564"

4
deploy/app/prod-build.sh

@ -6,7 +6,7 @@
#
# 用法(在 deploy/voitrans/ 下):
# ./prod-build.sh <version> # 只要版本号(应用共用同一镜像,构建不分应用);version 必须形如 X.Y.Z,如 0.1.1
# ./prod-build.sh <version> [公司] # 公司 = yunyan | lingpu(省略则交互选择;无 TTY 用默认 yunyan)
# ./prod-build.sh <version> [公司] # 公司 = yunyan | lingpu | along(省略则交互选择;无 TTY 用默认 yunyan)
set -euo pipefail
SVC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@ -50,5 +50,5 @@ docker push "$IMG_VER"
docker push "$IMG_LATEST"
green "✓ 已推送 $IMG_VER 与 $IMG_LATEST"
echo " 部署:./prod-deploy.sh <应用> <shanghai|japan|singapore> $VERSION"
echo " 部署:./prod-deploy.sh <应用> <shanghai|japan|singapore|along> $VERSION"
echo " 例:./prod-deploy.sh voitrans shanghai $VERSION"

35
deploy/app/prod-deploy.sh

@ -13,7 +13,8 @@
# ./prod-deploy.sh <应用> <区域> rollback # 回滚到上一次部署版本(与当前版本来回切换)
# ./prod-deploy.sh <区域> <tag> # 省略应用 → 默认首个应用(兼容旧写法)
# ./prod-deploy.sh -h # 查看用法
# 应用: 见 APPS(默认 voitrans) 区域: shanghai | japan | singapore
# 应用: 见 APPS(默认 voitrans) 区域: shanghai | japan | singapore | along
# 镜像仓库跟区域走:shanghai/japan/singapore → 云雁仓库,along → 阿龙仓库;需与 prod-build.sh 所选公司一致。
set -euo pipefail
SVC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@ -27,12 +28,13 @@ green() { printf "\033[32m%s\033[0m\n" "$*"; }
red() { printf "\033[31m%s\033[0m\n" "$*"; }
# ── 私有仓库凭据(写死;公开仓库前清空 REGISTRY_PASS)──
# 镜像仓库按公司选择:COMPANY=lingpu ./prod-deploy.sh …(默认 yunyan)
# 必须与 prod-build.sh 构建时所选公司一致,否则拉不到镜像。
COMPANY="${COMPANY:-$DEFAULT_COMPANY}"
registry_profile "$COMPANY" || exit 1
# 镜像仓库按「区域所属公司」自动选(见 region_profile 里的 REGION_COMPANY:三个云雁区域走 yunyan,
# 阿龙区域走 along 自己的 registry.ymaikj.com),也可用环境变量强制指定:COMPANY=lingpu ./prod-deploy.sh …
# 所选公司必须与 prod-build.sh 构建时所选公司一致,否则拉不到镜像。
COMPANY_OVERRIDE="${COMPANY:-}" # 环境变量显式指定的公司;留空 = 跟区域走
# 共用镜像:所有应用都用这一个。名字走 prod_image_name(),与 prod-build.sh 同源,防漂移。
IMAGE_NAME="$(prod_image_name "$COMPANY" app)" || exit 1
# 真正的 REGISTRY / IMAGE_NAME 要等选完区域才定(见 region_profile 之后),这里只取个展示用的名字。
IMAGE_HINT="$(prod_image_name "$DEFAULT_COMPANY" app)" || exit 1
DOCKER_NETWORK="1panel-network" # compose 外部网络名(与服务器一致)
# ╔══════════════════════════════════════════════════════════════════════╗
@ -68,9 +70,11 @@ DEFAULT_APP="${APPS[0]%%|*}" # 省略应用时的默认(首个)
# ║ 写死在此(本文件纳入 git)。公开仓库前清空 HOST/KEY。 ║
# ║ 新增区域:在 region_profile 加一段 case,并把区域名加进 REGIONS 数组。 ║
# ╚══════════════════════════════════════════════════════════════════════╝
REGIONS=(shanghai japan singapore) # 可选环境/区域(交互菜单按此顺序展示)
REGIONS=(shanghai japan singapore along) # 可选环境/区域(交互菜单按此顺序展示)
region_profile() { # 远端部署目录由「应用」决定(见 APPS),此处只管各区域服务器连接信息
REGION_COMPANY="$DEFAULT_COMPANY" # 该区域镜像仓库所属公司(默认云雁;可被环境变量 COMPANY 覆盖)
DEPLOY_DIR_OVERRIDE="" # 非空则覆盖 APPS 里该应用的远端目录(个别区域目录规范不同)
case "$REGION" in
shanghai)
DEPLOY_HOST="139.224.212.237" # ← 上海生产服务器 IP(必填)
@ -90,6 +94,14 @@ region_profile() { # 远端部署目录由「应
DEPLOY_USER=root
DEPLOY_KEY=/Users/liwei/Documents/key/alibaba_singapore.pem # ← 新加坡服务器 SSH 私钥(各区域不同)
;;
along)
DEPLOY_HOST="47.116.104.181" # ← 阿龙生产服务器 IP
DEPLOY_PORT=22
DEPLOY_USER=root
DEPLOY_KEY=~/.ssh/along.pem # 与阿龙测试机同一把钥匙;原件在 ~/Documents/阿龙/loginscre.pem,因 macOS TCC 挡文稿目录已拷贝到 ~/.ssh
REGION_COMPANY="along" # 镜像从 registry.ymaikj.com 拉(实体在阿龙测试机 8.133.166.29,账号 admin)
DEPLOY_DIR_OVERRIDE="/home/work/starpivot/app" # 阿龙机沿用测试环境的目录规范,不用 APPS 里的 /home/work/<应用>-a11
;;
*) red "✗ 未知区域: ${REGION}(应为 ${REGIONS[*]// /|})"; exit 1 ;;
esac
}
@ -99,7 +111,7 @@ region_host_of() { ( REGION="$1" region_profile >/dev/null 2>&1; printf '%s' "${
# ── 用法 ──
usage() {
blue "生产部署(共用镜像 ${IMAGE_NAME},仅远端目录按应用区分)"
blue "生产部署(共用镜像 ${IMAGE_HINT},仅远端目录按应用区分)"
cat >&2 <<EOF
用法: ./prod-deploy.sh <应用> <环境/区域> <tag|rollback>
应用 : $(app_keys | paste -sd '/' -)
@ -201,6 +213,13 @@ region_profile
: "${DEPLOY_HOST:?请在 prod-deploy.sh 的 region_profile($REGION) 段填该区域 IP}"
: "${DEPLOY_KEY:?请在 prod-deploy.sh 的 region_profile($REGION) 段填该区域 SSH 私钥}"
# 区域定了才知道拉哪个仓库:默认跟区域走,环境变量 COMPANY 可强制覆盖
COMPANY="${COMPANY_OVERRIDE:-$REGION_COMPANY}"
registry_profile "$COMPANY" || exit 1
IMAGE_NAME="$(prod_image_name "$COMPANY" app)" || exit 1
# 个别区域(阿龙)目录规范与 APPS 里不同,此处覆盖
if [ -n "$DEPLOY_DIR_OVERRIDE" ]; then DEPLOY_DIR="$DEPLOY_DIR_OVERRIDE"; fi
# 交互模式下,部署前二次确认(带全参数的非交互/CI 调用不打断)
if [ "$INTERACTIVE" = "1" ]; then
echo >&2

4
deploy/console/README.md

@ -55,7 +55,7 @@ cd deploy/console
./dev-deploy.sh yunyan # 开发:发布到云雁测试环境
./dev-deploy.sh # 不带参数 → 交互选择测试环境
./prod-build.sh 0.1.1 yunyan # 生产①:构建并推送 starpivot-console:0.1.1 (+latest);第2参数选公司
./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu),省略环境则交互选
./prod-deploy.sh 0.1.1 lingpu # 生产②:部署 0.1.1 到指定生产环境(yunyan|lingpu|along),省略环境则交互选
./prod-deploy.sh rollback lingpu # 生产回滚到上一版本(与当前版本来回切换)
```
@ -64,7 +64,7 @@ cd deploy/console
## 需要填写 / 提供的东西
- **部署目标与凭据**:写死在各脚本顶部。`dev-deploy.sh` 的 `env_profile()`(每个测试环境一套 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`,现有 `lingpu`、`yunyan`;新增环境加一段 case 并写进 `ENVS`)、`prod-deploy.sh` 的 `env_profile()`(每个生产环境一套 `REGISTRY/IMAGE_NAME/DEPLOY_HOST/DEPLOY_KEY/DEPLOY_DIR`,现有 `yunyan`、`lingpu`;新增环境加一段 case 并写进 `PROD_ENVS`);
- **部署目标与凭据**:写死在各脚本顶部。`dev-deploy.sh` 的 `env_profile()`(每个测试环境一套 `REGISTRY/DEV_HOST/DEV_KEY/DEV_DIR`,现有 `lingpu`、`yunyan`、`along`;新增环境加一段 case 并写进 `ENVS`)、`prod-deploy.sh` 的 `env_profile()`(每个生产环境一套 `REGISTRY/IMAGE_NAME/DEPLOY_HOST/DEPLOY_KEY/DEPLOY_DIR`,现有 `yunyan`、`lingpu`、`along`;新增环境加一段 case 并写进 `PROD_ENVS`);
仓库账号 `admin` / 密码已写死;`DEPLOY_KEY` 默认 `~/.ssh/id_rsa`。
- ⚠️ 本目录脚本纳入 git,**公开仓库前务必清空** `*_HOST` 与 `REGISTRY_PASS`。
- **依赖端点**:填服务器 `.env` 的 `POSTGRES_DSN / REDIS_* / NATS_URL`,以及 `CONSOLE_TOKEN_KEY`、

2
deploy/console/prod-build.sh

@ -6,7 +6,7 @@
#
# 用法(在 deploy/console/ 下):
# ./prod-build.sh <version> # version 必须形如 X.Y.Z,如 0.1.1
# ./prod-build.sh <version> [公司] # 公司 = yunyan | lingpu(省略则交互选择;无 TTY 用默认 yunyan)
# ./prod-build.sh <version> [公司] # 公司 = yunyan | lingpu | along(省略则交互选择;无 TTY 用默认 yunyan)
set -euo pipefail
SVC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

11
deploy/console/prod-deploy.sh

@ -8,7 +8,7 @@
# 用法(在 deploy/console/ 下):
# ./prod-deploy.sh <tag> [环境] # 部署指定版本,如 ./prod-deploy.sh 0.1.1 lingpu
# ./prod-deploy.sh rollback [环境] # 回滚到上一次部署版本(与当前版本来回切换)
# 环境省略 → 交互选择;无 TTY 时用默认环境 yunyan。
# 环境 = yunyan(上海)| lingpu(广州)| along(阿龙 47.116.104.181);省略 → 交互选择,无 TTY 时用默认 yunyan。
# ⚠️ 环境必须与 prod-build.sh 构建时所选公司一致,否则拉不到镜像。
set -euo pipefail
@ -32,7 +32,7 @@ DOCKER_NETWORK="1panel-network" # compose 外部网络名(与服务
# 容器名:必须与 docker-compose.yml 的 container_name 一字不差(本脚本会上传覆盖那份 compose)。
# 仅用于 up 冲突时清理同名遗留容器;不随环境变化。
CONTAINER_NAME="starpivot-console"
PROD_ENVS=(yunyan lingpu) # 可选生产环境(交互菜单按此顺序展示)
PROD_ENVS=(yunyan lingpu along) # 可选生产环境(交互菜单按此顺序展示)
DEFAULT_PROD_ENV="yunyan" # 无参数且无 TTY(CI/管道)时的默认环境
env_profile() {
@ -56,6 +56,13 @@ env_profile() {
DEPLOY_KEY=~/.ssh/lingpu-gz.pem # 私钥放 ~/.ssh:~/Documents 会被 macOS TCC 挡住读不出来
DEPLOY_DIR="/home/work/starpivot/console"
;;
along)
ENV_DESC="阿龙生产环境 · 阿里云"
registry_profile along || return 1 # 镜像仓库:registry.ymaikj.com(实体在阿龙测试机 8.133.166.29,账号 admin)
DEPLOY_HOST="47.116.104.181" # ← 阿龙生产服务器 IP
DEPLOY_KEY=~/.ssh/along.pem # 与测试机同一把钥匙;原件在 ~/Documents/阿龙/loginscre.pem,因 macOS TCC 挡文稿目录已拷贝到 ~/.ssh
DEPLOY_DIR="/home/work/starpivot/console"
;;
*) red "✗ 未知环境: ${DEPLOY_ENV}(应为 ${PROD_ENVS[*]// /|})"; exit 1 ;;
esac
IMAGE_NAME="$(prod_image_name "$DEPLOY_ENV" console)" || return 1

9
deploy/registry-profiles.sh

@ -13,7 +13,7 @@
# 公开仓库前务必清空 REGISTRY_PASS。
set -euo pipefail
COMPANIES=(yunyan lingpu) # 可选公司(交互菜单按此顺序展示;along 只用于测试环境,不进生产菜单)
COMPANIES=(yunyan lingpu along) # 可选公司(交互菜单按此顺序展示;along 2026-08-16 起也有正式环境,故进生产菜单)
DEFAULT_COMPANY="yunyan" # 不指定且无 TTY(CI/管道)时用哪个
# 按公司设置仓库三件套。失败返回非 0,由调用方决定是否 exit。
@ -33,7 +33,8 @@ registry_profile() {
;;
along)
COMPANY_DESC="阿龙"
REGISTRY="registry.ymaikj.com" # 仓库实体就在阿龙机 8.133.166.29 上(NPM 反代 → docker-registry:5000)
REGISTRY="registry.ymaikj.com" # 仓库实体就在阿龙测试机 8.133.166.29 上(NPM 反代 → docker-registry:5000)
# 正式机 47.116.104.181 也从这里拉镜像(阿龙只此一个仓库)
REGISTRY_USER="admin" # 注意:账号是 admin,与云雁/灵谱的 liwei 不同
REGISTRY_PASS="li13451234"
;;
@ -47,13 +48,13 @@ registry_profile() {
# 生产镜像名按公司区分。prod-build.sh 与 prod-deploy.sh 都调它,避免两处各写一份映射后漂移
# ——构建推的名字和部署拉的名字对不上,报错要到远端 docker pull 才暴露。
#
# admin/console:两家都用 starpivot-* 新命名,与 docker-compose.yml 里写死的 image/container_name
# admin/console:各公司都用 starpivot-* 新命名,与 docker-compose.yml 里写死的 image/container_name
# 一致。云雁的 admin/console 生产目标已于 2026-08-05 迁到上海机(那台没有历史容器),
# 旧名 yunyan-admin / console-a11 只留在待下线的美国机 47.253.88.66 上,不再由脚本部署。
# app:2026-08-10 起统一为 starpivot-app。旧名 voitrans-a11 已废弃——仓库里的旧 tag 不再更新,
# 首次用新名部署需先 ./prod-build.sh <version> 推一版(旧 tag 不会自动搬过来)。
#
# 现在三个服务、两家公司都是 starpivot-<服务>,函数保留公司参数只为兼容调用方和将来再分叉。
# 现在三个服务、各家公司都是 starpivot-<服务>,函数保留公司参数只为兼容调用方和将来再分叉。
#
# 用法:IMAGE_NAME="$(prod_image_name lingpu admin)"
prod_image_name() { # $1=公司 $2=服务(admin|console|app)

Loading…
Cancel
Save