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
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] + "…"
|
|
}
|
|
|