package configure import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" "git.x2erp.com/qdy/go-base/model/response" ) // Client 配置中心客户端 type Client struct { config ClientConfig httpClient *http.Client } // ResponseWrapper 包装API响应 type ResponseWrapper[T any] struct { response.QueryResult[T] } // NewClient 创建新的配置中心客户端 func NewClient() (*Client, error) { config := DefaultConfig() return NewClientWithConfig(config) } // NewClientWithConfig 使用自定义配置创建客户端 func NewClientWithConfig(config ClientConfig) (*Client, error) { // 验证配置 if err := config.Validate(); err != nil { return nil, err } // 创建HTTP客户端 httpClient := &http.Client{ Timeout: config.HTTPTimeout, } return &Client{ config: config, httpClient: httpClient, }, nil } // NewBasicAuthClient 创建使用Basic认证的客户端 func NewBasicAuthClient(baseURL, username, password string) (*Client, error) { config := ClientConfig{ BaseURL: baseURL, AuthType: AuthTypeBasic, Username: username, Password: password, HTTPTimeout: 30 * time.Second, } return NewClientWithConfig(config) } // NewTokenAuthClient 创建使用Token认证的客户端 func NewTokenAuthClient(baseURL, token string) (*Client, error) { config := ClientConfig{ BaseURL: baseURL, AuthType: AuthTypeToken, Token: token, HTTPTimeout: 30 * time.Second, } return NewClientWithConfig(config) } // ListTables 查询数据库表字典列表 func (c *Client) ListTables(ctx context.Context, query *DicTableQueryRequest) (*DicTableList, error) { endpoint := "/api/dic-table/list" var result ResponseWrapper[[]DicTableDB] if err := c.doRequest(ctx, http.MethodPost, endpoint, query, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("list_tables", fmt.Sprintf("API error: %s", result.Error), nil) } return &DicTableList{ TotalCount: result.TotalCount, LastPage: result.LastPage, Data: result.Data, }, nil } // GetTable 查询数据库表字典详情 func (c *Client) GetTable(ctx context.Context, tableID string) (*DicTableDetail, error) { endpoint := fmt.Sprintf("/api/dic-table/detail/%s", tableID) var result ResponseWrapper[DicTableDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, nil, &result, false); err != nil { return nil, err } if !result.Success { // 检查是否是"未找到"错误 if result.Error == "not found" || result.Error == "数据库表字典不存在" || result.Error == "查询数据库表字典主表失败: sql: no rows in result set" || result.Error == "查询数据库表字典主表失败: context canceled" { return nil, ErrNotFound } // 检查是否是context取消错误 if result.Error == "查询数据库表字典主表失败: context canceled" || strings.Contains(result.Error, "context canceled") || strings.Contains(result.Error, "context deadline exceeded") { return nil, WrapError("get_table", "context canceled", nil) } return nil, NewClientError("get_table", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // SaveTable 创建或更新数据库表字典 func (c *Client) SaveTable(ctx context.Context, req *DicTableRequest) (*DicTableDetail, error) { endpoint := "/api/dic-table/save" var result ResponseWrapper[DicTableDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("save_table", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // DeleteTable 删除数据库表字典 func (c *Client) DeleteTable(ctx context.Context, tableID string) error { endpoint := fmt.Sprintf("/api/dic-table/delete/%s", tableID) var result ResponseWrapper[int64] if err := c.doRequest(ctx, http.MethodPost, endpoint, nil, &result, false); err != nil { return err } if !result.Success { return NewClientError("delete_table", fmt.Sprintf("API error: %s", result.Error), nil) } return nil } // BatchSaveTables 批量保存数据库表字典 func (c *Client) BatchSaveTables(ctx context.Context, req *BatchSaveDicTablesRequest) error { endpoint := "/api/dic-table/batch-save" var result ResponseWrapper[bool] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result, false); err != nil { return err } if !result.Success { return NewClientError("batch_save_tables", fmt.Sprintf("API error: %s", result.Error), nil) } return nil } // BatchSyncTables 批量同步表字典 func (c *Client) BatchSyncTables(ctx context.Context, req *BatchTableSyncRequest) (*BatchTableSyncResponse, error) { endpoint := "/api/dic-table/batch-sync" var result ResponseWrapper[BatchTableSyncResponse] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("batch_sync_tables", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // BatchSyncAliases 批量同步别名 func (c *Client) BatchSyncAliases(ctx context.Context, req *BatchAliasSyncRequest) (*BatchAliasSyncResponse, error) { endpoint := "/api/alias/batch-sync" var result ResponseWrapper[BatchAliasSyncResponse] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("batch_sync_aliases", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // SaveTableAlias 保存表别名字典 func (c *Client) SaveTableAlias(ctx context.Context, req *TableAliasRequest) (*TableAliasResponse, error) { endpoint := "/api/dic-table-alias/save" var result ResponseWrapper[TableAliasResponse] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("save_table_alias", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // SaveTableFieldAlias 保存表字段别名字典 func (c *Client) SaveTableFieldAlias(ctx context.Context, req *TableFieldAliasRequest) (*TableFieldAliasResponse, error) { endpoint := "/api/dic-table-field-alias/save" var result ResponseWrapper[TableFieldAliasResponse] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("save_table_field_alias", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // ListTableAliases 查询表别名字典列表 func (c *Client) ListTableAliases(ctx context.Context, query *TableAliasQueryRequest) (*TableAliasList, error) { endpoint := "/api/dic-table-alias/list" var result ResponseWrapper[[]TableAliasResponse] if err := c.doRequest(ctx, http.MethodPost, endpoint, query, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("list_table_aliases", fmt.Sprintf("API error: %s", result.Error), nil) } return &TableAliasList{ TotalCount: result.TotalCount, LastPage: result.LastPage, Data: result.Data, }, nil } // ListTableFieldAliases 查询表字段别名字典列表 func (c *Client) ListTableFieldAliases(ctx context.Context, query *TableFieldAliasQueryRequest) (*TableFieldAliasList, error) { endpoint := "/api/dic-table-field-alias/list" var result ResponseWrapper[[]TableFieldAliasResponse] if err := c.doRequest(ctx, http.MethodPost, endpoint, query, &result, false); err != nil { return nil, err } if !result.Success { return nil, NewClientError("list_table_field_aliases", fmt.Sprintf("API error: %s", result.Error), nil) } return &TableFieldAliasList{ TotalCount: result.TotalCount, LastPage: result.LastPage, Data: result.Data, }, nil } // doRequest 执行HTTP请求 func (c *Client) doRequest(ctx context.Context, method, endpoint string, body interface{}, result interface{}, skipAuth bool) error { // 构建URL url := c.config.BaseURL + endpoint // 准备请求体 var requestBody io.Reader if body != nil { jsonData, err := json.Marshal(body) if err != nil { return WrapError("do_request", "failed to marshal request body", err) } requestBody = bytes.NewBuffer(jsonData) } // 创建请求 req, err := http.NewRequestWithContext(ctx, method, url, requestBody) if err != nil { return WrapError("do_request", "failed to create request", err) } // 设置认证头(可选) if !skipAuth { if err := c.setAuthHeader(req); err != nil { return err } } // 设置内容类型 // 对于POST、PUT、PATCH方法,即使body为nil也设置Content-Type if body != nil || method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch { req.Header.Set("Content-Type", "application/json") } // 执行请求 resp, err := c.httpClient.Do(req) if err != nil { if ctx.Err() == context.DeadlineExceeded { return ErrRequestTimeout } return WrapError("do_request", "HTTP request failed", err) } defer resp.Body.Close() // 读取响应体 respBody, err := io.ReadAll(resp.Body) if err != nil { return WrapError("do_request", "failed to read response body", err) } // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { switch resp.StatusCode { case http.StatusUnauthorized: return ErrUnauthorized case http.StatusForbidden: return ErrForbidden case http.StatusNotFound: return ErrNotFound case http.StatusBadRequest: return ErrValidationFailed default: return NewClientError("do_request", fmt.Sprintf("HTTP error %d: %s", resp.StatusCode, string(respBody)), nil) } } // 解析响应 if err := json.Unmarshal(respBody, result); err != nil { return WrapError("do_request", "failed to unmarshal response", err) } return nil } // setAuthHeader 设置认证头 func (c *Client) setAuthHeader(req *http.Request) error { switch c.config.AuthType { case AuthTypeBasic: req.SetBasicAuth(c.config.Username, c.config.Password) case AuthTypeToken: req.Header.Set("Authorization", "Bearer "+c.config.Token) case AuthTypeNone: // 无认证,不设置任何认证头 default: return ErrConfigInvalidAuthType } return nil } // LoginUser 用户登录(无需认证) func (c *Client) LoginUser(ctx context.Context, req *UserLoginRequest) (string, error) { endpoint := "/api/login/user" var result ResponseWrapper[string] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result, true); err != nil { return "", err } if !result.Success { return "", NewClientError("login_user", fmt.Sprintf("API error: %s", result.Error), nil) } return result.Data, nil } // GetConfig 获取客户端配置 func (c *Client) GetConfig() ClientConfig { return c.config }