refactor: re-implement mohua services with custom sdk, to lightweight package size

This commit is contained in:
Fu Diwei
2026-06-22 16:32:18 +08:00
committed by RHQYZ
parent d9288b91c3
commit f7f0864e3b
11 changed files with 330 additions and 34 deletions
-1
View File
@@ -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
-2
View File
@@ -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=
+1 -1
View File
@@ -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
})
@@ -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
}
@@ -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
}
@@ -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
}
+160
View File
@@ -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
}
+10
View File
@@ -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"`
}
+22
View File
@@ -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
}
}
+21
View File
@@ -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)
+2 -2
View File
@@ -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)
}
}