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); 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); 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); 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); err != nil { return err } if !result.Success { return NewClientError("delete_table", fmt.Sprintf("API error: %s", result.Error), nil) } return nil } // ListTableAliases 查询表别名字典列表 func (c *Client) ListTableAliases(ctx context.Context, query *TableAliasQueryRequest) (*TableAliasList, error) { endpoint := "/api/dic-table-alias/list" var result ResponseWrapper[[]DicTableAliasDB] if err := c.doRequest(ctx, http.MethodPost, endpoint, query, &result); 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 } // GetTableAlias 查询表别名字典详情 func (c *Client) GetTableAlias(ctx context.Context, id string) (*TableAliasDetail, error) { endpoint := fmt.Sprintf("/api/dic-table-alias/detail/%s", id) var result ResponseWrapper[TableAliasDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, nil, &result); err != nil { return nil, err } if !result.Success { if result.Error == "not found" || strings.Contains(result.Error, "不存在") { return nil, ErrNotFound } return nil, NewClientError("get_table_alias", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // SaveTableAlias 创建或更新表别名字典 func (c *Client) SaveTableAlias(ctx context.Context, req *TableAliasRequest) (*TableAliasDetail, error) { endpoint := "/api/dic-table-alias/save" var result ResponseWrapper[TableAliasDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); 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 } // BatchSaveTableAliases 批量保存表别名字典 func (c *Client) BatchSaveTableAliases(ctx context.Context, req *BatchTableAliasRequest) ([]TableAliasDetail, error) { endpoint := "/api/dic-table-alias/batch-save" var result ResponseWrapper[[]TableAliasDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); err != nil { return nil, err } if !result.Success { return nil, NewClientError("batch_save_table_aliases", fmt.Sprintf("API error: %s", result.Error), nil) } return result.Data, nil } // DeleteTableAlias 删除表别名字典 func (c *Client) DeleteTableAlias(ctx context.Context, id string) error { endpoint := fmt.Sprintf("/api/dic-table-alias/delete/%s", id) var result ResponseWrapper[int64] if err := c.doRequest(ctx, http.MethodPost, endpoint, nil, &result); err != nil { return err } if !result.Success { return NewClientError("delete_table_alias", fmt.Sprintf("API error: %s", result.Error), nil) } return nil } // ListTableFieldAliases 查询字段别名字典列表 func (c *Client) ListTableFieldAliases(ctx context.Context, query *TableFieldAliasQueryRequest) (*TableFieldAliasList, error) { endpoint := "/api/dic-table-field-alias/list" var result ResponseWrapper[[]DicTableFieldAliasDB] if err := c.doRequest(ctx, http.MethodPost, endpoint, query, &result); 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 } // GetTableFieldAlias 查询字段别名字典详情 func (c *Client) GetTableFieldAlias(ctx context.Context, id string) (*TableFieldAliasDetail, error) { endpoint := fmt.Sprintf("/api/dic-table-field-alias/detail/%s", id) var result ResponseWrapper[TableFieldAliasDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, nil, &result); err != nil { return nil, err } if !result.Success { if result.Error == "not found" || strings.Contains(result.Error, "不存在") { return nil, ErrNotFound } return nil, NewClientError("get_table_field_alias", fmt.Sprintf("API error: %s", result.Error), nil) } return &result.Data, nil } // SaveTableFieldAlias 创建或更新字段别名字典 func (c *Client) SaveTableFieldAlias(ctx context.Context, req *TableFieldAliasRequest) (*TableFieldAliasDetail, error) { endpoint := "/api/dic-table-field-alias/save" var result ResponseWrapper[TableFieldAliasDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); 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 } // BatchSaveTableFieldAliases 批量保存字段别名字典 func (c *Client) BatchSaveTableFieldAliases(ctx context.Context, req *BatchTableFieldAliasRequest) ([]TableFieldAliasDetail, error) { endpoint := "/api/dic-table-field-alias/batch-save" var result ResponseWrapper[[]TableFieldAliasDetail] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); err != nil { return nil, err } if !result.Success { return nil, NewClientError("batch_save_table_field_aliases", fmt.Sprintf("API error: %s", result.Error), nil) } return result.Data, nil } // DeleteTableFieldAlias 删除字段别名字典 func (c *Client) DeleteTableFieldAlias(ctx context.Context, id string) error { endpoint := fmt.Sprintf("/api/dic-table-field-alias/delete/%s", id) var result ResponseWrapper[int64] if err := c.doRequest(ctx, http.MethodPost, endpoint, nil, &result); err != nil { return err } if !result.Success { return NewClientError("delete_table_field_alias", fmt.Sprintf("API error: %s", result.Error), nil) } return nil } // ListTableAliasFlow 查询表别名字典流水列表 func (c *Client) ListTableAliasFlow(ctx context.Context, query *TableAliasFlowQueryRequest) (*TableAliasFlowList, error) { endpoint := "/api/dic-table-alias-flow/list" var result ResponseWrapper[[]DicTableAliasFlowDB] if err := c.doRequest(ctx, http.MethodPost, endpoint, query, &result); err != nil { return nil, err } if !result.Success { return nil, NewClientError("list_table_alias_flow", fmt.Sprintf("API error: %s", result.Error), nil) } return &TableAliasFlowList{ TotalCount: result.TotalCount, LastPage: result.LastPage, Data: result.Data, }, nil } // BatchSaveTableAliasFlow 批量保存表别名字典流水 func (c *Client) BatchSaveTableAliasFlow(ctx context.Context, req *BatchTableAliasRequest, tenantID string) ([]DicTableAliasFlowDB, error) { endpoint := fmt.Sprintf("/api/dic-table-alias-flow/batch-save?tenantID=%s", tenantID) var result ResponseWrapper[[]DicTableAliasFlowDB] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); err != nil { return nil, err } if !result.Success { return nil, NewClientError("batch_save_table_alias_flow", fmt.Sprintf("API error: %s", result.Error), nil) } return result.Data, nil } // BatchApprovalTableAliasFlow 批量审批表别名字典流水 func (c *Client) BatchApprovalTableAliasFlow(ctx context.Context, req *BatchApprovalFlowRequest) error { endpoint := "/api/dic-table-alias-flow/batch-approval" var result ResponseWrapper[int64] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); err != nil { return err } if !result.Success { return NewClientError("batch_approval_table_alias_flow", fmt.Sprintf("API error: %s", result.Error), nil) } return nil } // ListTableFieldAliasFlow 查询字段别名字典流水列表 func (c *Client) ListTableFieldAliasFlow(ctx context.Context, query *TableFieldAliasFlowQueryRequest) (*TableFieldAliasFlowList, error) { endpoint := "/api/dic-table-field-alias-flow/list" var result ResponseWrapper[[]DicTableFieldAliasFlowDB] if err := c.doRequest(ctx, http.MethodPost, endpoint, query, &result); err != nil { return nil, err } if !result.Success { return nil, NewClientError("list_table_field_alias_flow", fmt.Sprintf("API error: %s", result.Error), nil) } return &TableFieldAliasFlowList{ TotalCount: result.TotalCount, LastPage: result.LastPage, Data: result.Data, }, nil } // BatchSaveTableFieldAliasFlow 批量保存字段别名字典流水 func (c *Client) BatchSaveTableFieldAliasFlow(ctx context.Context, req *BatchTableFieldAliasRequest, tenantID string) ([]DicTableFieldAliasFlowDB, error) { endpoint := fmt.Sprintf("/api/dic-table-field-alias-flow/batch-save?tenantID=%s", tenantID) var result ResponseWrapper[[]DicTableFieldAliasFlowDB] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); err != nil { return nil, err } if !result.Success { return nil, NewClientError("batch_save_table_field_alias_flow", fmt.Sprintf("API error: %s", result.Error), nil) } return result.Data, nil } // BatchApprovalTableFieldAliasFlow 批量审批字段别名字典流水 func (c *Client) BatchApprovalTableFieldAliasFlow(ctx context.Context, req *BatchApprovalFlowRequest) error { endpoint := "/api/dic-table-field-alias-flow/batch-approval" var result ResponseWrapper[int64] if err := c.doRequest(ctx, http.MethodPost, endpoint, req, &result); err != nil { return err } if !result.Success { return NewClientError("batch_approval_table_field_alias_flow", fmt.Sprintf("API error: %s", result.Error), nil) } return nil } // doRequest 执行HTTP请求 func (c *Client) doRequest(ctx context.Context, method, endpoint string, body interface{}, result interface{}) 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 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) default: return ErrConfigInvalidAuthType } return nil } // GetConfig 获取客户端配置 func (c *Client) GetConfig() ClientConfig { return c.config }