mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
refactor: re-implement mohua services with custom sdk, to lightweight package size
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user