From f7f0864e3bc929d15c372ea02a7ff59fc9fd0303 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 16 Jun 2026 10:05:39 +0800 Subject: [PATCH] refactor: re-implement mohua services with custom sdk, to lightweight package size --- go.mod | 1 - go.sum | 2 - internal/certmgmt/deployers/sp_mohua_mvh.go | 2 +- .../deployer/providers/mohua-mvh/mohua_mvh.go | 39 ++--- .../mohua/api_list_virtualhost_domains.go | 46 +++++ pkg/sdk3rd/mohua/api_set_virtualhost_ssl.go | 57 +++++++ pkg/sdk3rd/mohua/client.go | 160 ++++++++++++++++++ pkg/sdk3rd/mohua/models.go | 10 ++ pkg/sdk3rd/mohua/options.go | 22 +++ pkg/sdk3rd/mohua/types.go | 21 +++ pkg/sdk3rd/nginxproxymanager/client.go | 4 +- 11 files changed, 330 insertions(+), 34 deletions(-) create mode 100644 pkg/sdk3rd/mohua/api_list_virtualhost_domains.go create mode 100644 pkg/sdk3rd/mohua/api_set_virtualhost_ssl.go create mode 100644 pkg/sdk3rd/mohua/client.go create mode 100644 pkg/sdk3rd/mohua/models.go create mode 100644 pkg/sdk3rd/mohua/options.go create mode 100644 pkg/sdk3rd/mohua/types.go diff --git a/go.mod b/go.mod index 2a2475525..9425486db 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,6 @@ require ( github.com/jlaffaye/ftp v0.2.0 github.com/microcosm-cc/bluemonday v1.0.27 github.com/minio/minio-go/v7 v7.2.0 - github.com/mohuatech/mohuacloud-go-sdk v0.0.0-20251115182757-6fba4d0a4c47 github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 github.com/pkg/sftp v1.13.10 github.com/pocketbase/dbx v1.12.0 diff --git a/go.sum b/go.sum index 570d91eaf..63e091f37 100644 --- a/go.sum +++ b/go.sum @@ -722,8 +722,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohuatech/mohuacloud-go-sdk v0.0.0-20251115182757-6fba4d0a4c47 h1:ymaxpfg8BH3Jlecq943X/+QWOBuMp1qmRUCK+SCoN+c= -github.com/mohuatech/mohuacloud-go-sdk v0.0.0-20251115182757-6fba4d0a4c47/go.mod h1:+GS72hJwcVILclv1ghdmowvKX+iT9gS42bhYLw9hcQg= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= diff --git a/internal/certmgmt/deployers/sp_mohua_mvh.go b/internal/certmgmt/deployers/sp_mohua_mvh.go index 8808bc967..44a18f954 100644 --- a/internal/certmgmt/deployers/sp_mohua_mvh.go +++ b/internal/certmgmt/deployers/sp_mohua_mvh.go @@ -20,7 +20,7 @@ func init() { Username: credentials.Username, ApiPassword: credentials.ApiPassword, HostId: xmaps.GetString(options.ProviderExtendedConfig, "hostId"), - DomainId: xmaps.GetString(options.ProviderExtendedConfig, "domainId"), + DomainId: xmaps.GetInt64(options.ProviderExtendedConfig, "domainId"), }) return provider, err }) diff --git a/pkg/core/deployer/providers/mohua-mvh/mohua_mvh.go b/pkg/core/deployer/providers/mohua-mvh/mohua_mvh.go index 974418c06..56d1a8d99 100644 --- a/pkg/core/deployer/providers/mohua-mvh/mohua_mvh.go +++ b/pkg/core/deployer/providers/mohua-mvh/mohua_mvh.go @@ -4,12 +4,9 @@ import ( "context" "fmt" "log/slog" - "strconv" - - mohuasdk "github.com/mohuatech/mohuacloud-go-sdk" - mohuasdktypes "github.com/mohuatech/mohuacloud-go-sdk/types" "github.com/certimate-go/certimate/pkg/core" + mohuasdk "github.com/certimate-go/certimate/pkg/sdk3rd/mohua" ) type ( @@ -25,7 +22,7 @@ type DeployerConfig struct { // 虚拟主机 ID。 HostId string `json:"hostId"` // 域名 ID。 - DomainId string `json:"domainId"` + DomainId int64 `json:"domainId"` } type Deployer struct { @@ -65,28 +62,17 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep if d.config.HostId == "" { return nil, fmt.Errorf("config `hostId` is required") } - if d.config.DomainId == "" { + if d.config.DomainId == 0 { return nil, fmt.Errorf("config `domainId` is required") } - domainId, err := strconv.ParseInt(d.config.DomainId, 10, 64) - if err != nil { - return nil, err - } - - // 登录获取 Token - _, err = d.sdkClient.Auth.Login("", "") - if err != nil { - return nil, fmt.Errorf("failed to login mohua: %w", err) - } - // 设置 SSL 证书 - setSSLReq := &mohuasdktypes.SetSSLRequest{ - ID: int(domainId), + setSSLReq := &mohuasdk.SetVirtualHostSSLRequest{ + ID: int(d.config.DomainId), SSLCert: certPEM, SSLKey: privkeyPEM, } - setSSLResp, err := d.sdkClient.VirtualHost.SetSSL(d.config.HostId, setSSLReq) + setSSLResp, err := d.sdkClient.SetVirtualHostSSL(d.config.HostId, setSSLReq) d.logger.Debug("sdk request 'mvh.SetSSL'", slog.Any("request", setSSLReq), slog.Any("response", setSSLResp)) if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'mvh.SetSSL': %w", err) @@ -96,15 +82,12 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep } func createSDKClient(username, apiPassword string) (*mohuasdk.Client, error) { - if username == "" { - return nil, fmt.Errorf("mohua: invalid username") - } - if apiPassword == "" { - return nil, fmt.Errorf("mohua: invalid api password") + client, err := mohuasdk.NewClient( + mohuasdk.WithLogins(username, apiPassword), + ) + if err != nil { + return nil, err } - client := mohuasdk.NewClient( - mohuasdk.WithCredentials(username, apiPassword), - ) return client, nil } diff --git a/pkg/sdk3rd/mohua/api_list_virtualhost_domains.go b/pkg/sdk3rd/mohua/api_list_virtualhost_domains.go new file mode 100644 index 000000000..413a214ba --- /dev/null +++ b/pkg/sdk3rd/mohua/api_list_virtualhost_domains.go @@ -0,0 +1,46 @@ +package mohua + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +type ListVirtualHostDomainsResponse struct { + sdkResponseBase + + Data []*DomainInfo `json:"data"` +} + +func (c *Client) ListVirtualHostDomains(hostId string) (*ListVirtualHostDomainsResponse, error) { + return c.ListVirtualHostDomainsWithContext(context.Background(), hostId) +} + +func (c *Client) ListVirtualHostDomainsWithContext(ctx context.Context, hostId string) (*ListVirtualHostDomainsResponse, error) { + if hostId == "" { + return nil, fmt.Errorf("sdkerr: bad request: unset hostId") + } + + if err := c.ensureJwtTokenExists(); err != nil { + return nil, err + } + + path := fmt.Sprintf("/provision/custom/%s/domains", url.PathEscape(hostId)) + httpreq, err := c.newRequest(http.MethodPost, path) + if err != nil { + return nil, err + } else { + httpreq.SetBody(map[string]any{ + "func": "ListDomain", + }) + httpreq.SetContext(ctx) + } + + result := &ListVirtualHostDomainsResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/mohua/api_set_virtualhost_ssl.go b/pkg/sdk3rd/mohua/api_set_virtualhost_ssl.go new file mode 100644 index 000000000..c30baddcc --- /dev/null +++ b/pkg/sdk3rd/mohua/api_set_virtualhost_ssl.go @@ -0,0 +1,57 @@ +package mohua + +import ( + "context" + "fmt" + "net/http" + "net/url" +) + +type SetVirtualHostSSLRequest struct { + ID int `json:"id"` + SSLForce string `json:"ssl_force"` + SSLCert string `json:"sslCert"` + SSLKey string `json:"sslKey"` +} + +type SetVirtualHostSSLResponse struct { + sdkResponseBase + + Data []*DomainInfo `json:"data"` +} + +func (c *Client) SetVirtualHostSSL(hostId string, req *SetVirtualHostSSLRequest) (*SetVirtualHostSSLResponse, error) { + return c.SetVirtualHostSSLWithContext(context.Background(), hostId, req) +} + +func (c *Client) SetVirtualHostSSLWithContext(ctx context.Context, hostId string, req *SetVirtualHostSSLRequest) (*SetVirtualHostSSLResponse, error) { + if hostId == "" { + return nil, fmt.Errorf("sdkerr: bad request: unset hostId") + } + + if err := c.ensureJwtTokenExists(); err != nil { + return nil, err + } + + path := fmt.Sprintf("/provision/custom/%s/domains", url.PathEscape(hostId)) + httpreq, err := c.newRequest(http.MethodPost, path) + if err != nil { + return nil, err + } else { + httpreq.SetBody(map[string]any{ + "func": "SetSSL", + "id": req.ID, + "ssl_force": req.SSLForce, + "sslCert": url.QueryEscape(req.SSLCert), + "sslKey": url.QueryEscape(req.SSLKey), + }) + httpreq.SetContext(ctx) + } + + result := &SetVirtualHostSSLResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return result, err + } + + return result, nil +} diff --git a/pkg/sdk3rd/mohua/client.go b/pkg/sdk3rd/mohua/client.go new file mode 100644 index 000000000..b1a118778 --- /dev/null +++ b/pkg/sdk3rd/mohua/client.go @@ -0,0 +1,160 @@ +package mohua + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-resty/resty/v2" + + "github.com/certimate-go/certimate/internal/app" +) + +type Client struct { + username string + password string + + jwtToken string + jwtTokenMtx sync.Mutex + + rc *resty.Client +} + +func NewClient(optFns ...OptionsFunc) (*Client, error) { + opts := &Options{} + for _, fn := range optFns { + fn(opts) + } + + if opts.JwtToken == "" && (opts.Username == "" || opts.Password == "") { + return nil, fmt.Errorf("sdkerr: unset password or jwtToken") + } + + client := &Client{ + username: opts.Username, + password: opts.Password, + jwtToken: opts.JwtToken, + } + client.rc = resty.New(). + SetBaseURL("https://cloud.mhjz1.cn"). + SetHeader("Accept", "application/json"). + SetHeader("Content-Type", "application/json"). + SetHeader("User-Agent", app.AppUserAgent). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.jwtToken != "" { + req.Header.Set("JWT", "Bearer "+client.jwtToken) + } + + return nil + }) + + return client, nil +} + +func (c *Client) SetTimeout(timeout time.Duration) *Client { + c.rc.SetTimeout(timeout) + return c +} + +func (c *Client) SetTLSConfig(config *tls.Config) *Client { + c.rc.SetTLSClientConfig(config) + return c +} + +func (c *Client) newRequest(method string, path string) (*resty.Request, error) { + if method == "" { + return nil, fmt.Errorf("sdkerr: unset method") + } + if path == "" { + return nil, fmt.Errorf("sdkerr: unset path") + } + + req := c.rc.R() + req.Method = method + req.URL = path + return req, nil +} + +func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) { + if req == nil { + return nil, fmt.Errorf("sdkerr: nil request") + } + + // WARN: + // PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD. + + resp, err := req.Send() + if err != nil { + return resp, fmt.Errorf("sdkerr: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) doRequestWithResult(req *resty.Request, res interface{}) (*resty.Response, error) { + if req == nil { + return nil, fmt.Errorf("sdkerr: nil request") + } + + resp, err := c.doRequest(req) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &res) + } + return resp, err + } + + if len(resp.Body()) != 0 { + var errRes *sdkResponseBase + if err := json.Unmarshal(resp.Body(), &errRes); err == nil { + if rStatus := errRes.GetStatus(); rStatus != 200 { + return resp, fmt.Errorf("sdkerr: error='%d', msg='%s'", rStatus, errRes.GetMsg()) + } + } + + if err := json.Unmarshal(resp.Body(), &res); err != nil { + return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String()) + } + } + + return resp, nil +} + +func (c *Client) ensureJwtTokenExists() error { + c.jwtTokenMtx.Lock() + defer c.jwtTokenMtx.Unlock() + if c.jwtToken != "" { + return nil + } + + httpreq, err := c.newRequest(http.MethodPost, "/v1/login_api") + if err != nil { + return err + } else { + httpreq.SetBody(map[string]string{ + "account": c.username, + "password": c.password, + }) + } + + type tokensResponse struct { + sdkResponseBase + JWT string `json:"jwt"` + } + + result := &tokensResponse{} + if _, err := c.doRequestWithResult(httpreq, result); err != nil { + return err + } else if rStatus := result.GetStatus(); rStatus != 200 { + return fmt.Errorf("sdkerr: failed to create npm token: status='%d', msg='%s'", rStatus, result.GetMsg()) + } else { + c.jwtToken = result.JWT + } + + return nil +} diff --git a/pkg/sdk3rd/mohua/models.go b/pkg/sdk3rd/mohua/models.go new file mode 100644 index 000000000..ff5373c2b --- /dev/null +++ b/pkg/sdk3rd/mohua/models.go @@ -0,0 +1,10 @@ +package mohua + +type DomainInfo struct { + ID int `json:"id"` + HostID int `json:"host_id"` + UID int `json:"uid"` + Domain string `json:"domain"` + SSLCertID int `json:"ssl_cert_id"` + SSLForce int `json:"ssl_force"` +} diff --git a/pkg/sdk3rd/mohua/options.go b/pkg/sdk3rd/mohua/options.go new file mode 100644 index 000000000..adfc63d80 --- /dev/null +++ b/pkg/sdk3rd/mohua/options.go @@ -0,0 +1,22 @@ +package mohua + +type Options struct { + Username string + Password string + JwtToken string +} + +type OptionsFunc func(*Options) + +func WithLogins(username, password string) OptionsFunc { + return func(o *Options) { + o.Username = username + o.Password = password + } +} + +func WithJwtToken(jwtToken string) OptionsFunc { + return func(o *Options) { + o.JwtToken = jwtToken + } +} diff --git a/pkg/sdk3rd/mohua/types.go b/pkg/sdk3rd/mohua/types.go new file mode 100644 index 000000000..5f3407911 --- /dev/null +++ b/pkg/sdk3rd/mohua/types.go @@ -0,0 +1,21 @@ +package mohua + +type sdkResponse interface { + GetStatus() int + GetMsg() string +} + +type sdkResponseBase struct { + Status int `json:"status"` + Msg string `json:"msg"` +} + +func (r *sdkResponseBase) GetStatus() int { + return r.Status +} + +func (r *sdkResponseBase) GetMsg() string { + return r.Msg +} + +var _ sdkResponse = (*sdkResponseBase)(nil) diff --git a/pkg/sdk3rd/nginxproxymanager/client.go b/pkg/sdk3rd/nginxproxymanager/client.go index 31360e46c..fd480cfb6 100644 --- a/pkg/sdk3rd/nginxproxymanager/client.go +++ b/pkg/sdk3rd/nginxproxymanager/client.go @@ -120,8 +120,8 @@ func (c *Client) doRequestWithResult(req *resty.Request, res interface{}) (*rest if len(resp.Body()) != 0 { var errRes *sdkResponseBase if err := json.Unmarshal(resp.Body(), &errRes); err == nil { - if terror := errRes.GetError(); terror != "" { - return resp, fmt.Errorf("sdkerr: error='%s'", terror) + if rError := errRes.GetError(); rError != "" { + return resp, fmt.Errorf("sdkerr: error='%s'", rError) } }