You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

94 lines
3.4 KiB

package console
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"yunyan/comm"
)
// console → 业务部署的「热重载」调用。
//
// console 是单例服务(lego/base/single)、不接 ETCD/rpcx 集群,无法直接 RPC 业务服务,
// 只能走该部署的公网网关入口(app_registry.base_url)。鉴权用 ${FIELD_ENCRYPT_KEY} 的 HMAC 签名,
// 由网关校验(见 comm/consolesign.go)——复用这把两边本就必须一致的密钥,不再多引入一把要同步的。
const (
reloadRoute = "api_reloadmoduleconfig"
resetRoute = "api_resetmoduleconfig"
reloadTimeout = 5 * time.Second
)
var reloadClient = &http.Client{Timeout: reloadTimeout}
// callModuleConfigReload 通知目标部署重读业务库并热替换客户端。
func callModuleConfigReload(baseUrl, encKey string) (*comm.ConfigApplyReport, error) {
return callConsoleRoute(baseUrl, encKey, reloadRoute, "")
}
// callModuleConfigReset 让目标部署把某模块的配置还原为它自己 confs/*.yaml 的初始值。
// yaml 原值只存在于业务服务内部,故必须由它自己完成——明文密钥一步都不离开该服务。
func callModuleConfigReset(baseUrl, encKey, module string) (*comm.ConfigApplyReport, error) {
return callConsoleRoute(baseUrl, encKey, resetRoute, module)
}
// callConsoleRoute 调目标部署的 console 专用运维接口并返回它的回执。
// base_url 未填 / 网络不通 / 服务未起 / 验签失败 → 返回 error;调用方须如实呈现失败,
// 绝不能默认当成功。arg 会并入签名,网关校验后经 Meta 传给业务侧。
func callConsoleRoute(baseUrl, encKey, route, arg string) (*comm.ConfigApplyReport, error) {
base := strings.TrimRight(strings.TrimSpace(baseUrl), "/")
if base == "" {
return nil, fmt.Errorf("该部署未填写对外地址(base_url),无法通知它")
}
if !strings.HasPrefix(base, "http://") && !strings.HasPrefix(base, "https://") {
base = "https://" + base
}
ts := strconv.FormatInt(time.Now().Unix(), 10)
req, err := http.NewRequest(http.MethodPost, base+"/api/api/"+route, bytes.NewReader([]byte("{}")))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set(comm.ConsoleTsHeader, ts)
req.Header.Set(comm.ConsoleArgHeader, arg)
req.Header.Set(comm.ConsoleSignHeader, comm.SignConsoleCall(encKey, ts, route, arg))
resp, err := reloadClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("重载接口 HTTP %d: %s", resp.StatusCode, truncate(string(body), 200))
}
// 成功时 api 服务返回 ConfigApplyReport 的原样 JSON;出错时网关/业务返回 HttpResult{code,msg}。
// 用 Kind 是否落位来区分两者——HttpResult 里没有这个字段。
var report comm.ConfigApplyReport
if e := json.Unmarshal(body, &report); e == nil && report.Kind != "" {
return &report, nil
}
var hr struct {
Code int `json:"code"`
Message string `json:"msg"`
}
if json.Unmarshal(body, &hr) == nil && hr.Message != "" {
return nil, fmt.Errorf("重载被拒绝(code=%d): %s", hr.Code, hr.Message)
}
return nil, fmt.Errorf("重载接口返回无法识别的响应: %s", truncate(string(body), 200))
}
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "…"
}